From 7718ed6c32b70c0ab24f4416aabfb1ed8316dbc0 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sun, 27 Jun 2021 21:01:43 +0200 Subject: [PATCH] Lot's of bug fixes --- .idea/codeStyles/codeStyleConfig.xml | 5 + .../Laravel_29_09_20_08_53.xml | 2771 +++++ .../frameworkDescriptionVersion1.1.4.xsd | 47 + app/Cache/Cache.php | 185 +- app/ChangeEmail.php | 6 - app/Http/Controllers/Auth/LoginController.php | 22 +- .../Controllers/Auth/RegisterController.php | 2 +- app/Http/Controllers/ControlController.php | 45 + app/Http/Controllers/HomeController.php | 6 + .../Controllers/OpenAudioMCController.php | 70 + app/Http/Controllers/Panel/HomeController.php | 14 +- .../Controllers/Panel/MessageController.php | 63 + app/Http/Controllers/Panel/ShowController.php | 143 + .../Controllers/Panel/ShowsController.php | 125 + app/Http/Controllers/Panel/UMSController.php | 48 +- .../Controllers/Profile/ChangeController.php | 9 +- .../Profile/SecurityController.php | 6 +- app/Http/Controllers/RidecountController.php | 1 + app/Http/Controllers/ShowController.php | 10 +- .../Controllers/ToggleTwoFactorController.php | 5 +- app/Http/Controllers/TwoFactorController.php | 3 +- app/Http/Kernel.php | 1 + app/Http/Middleware/AdminOnly.php | 25 + .../Middleware/RedirectIfAuthenticated.php | 6 +- app/Mail/EmailChange.php | 40 + app/Notifications/SendMailChange.php | 49 - app/Rules/UUID.php | 5 +- app/Show.php | 2 +- app/ShowDate.php | 31 + app/User.php | 11 +- composer.json | 3 +- config/database.php | 9 + .../2014_10_12_000000_create_users_table.php | 2 + .../2019_09_20_141811_create_seats_table.php | 2 + public/assets/control.js | 212 + public/assets/css/ionicons.min.css | 11 - public/assets/css/panel.min.css | 34 +- public/assets/css/panel_default.min.css | 29 + public/assets/css/skin.css | 297 +- public/assets/css/skin_default.css | 334 + public/assets/css/style.css | 186 +- public/assets/css/style_default.css | 462 + public/assets/development/formats/bbcode.js | 2735 ++++ public/assets/development/formats/xhtml.js | 1248 ++ public/assets/development/icons/material.js | 139 + public/assets/development/icons/monocons.js | 119 + .../development/jquery.sceditor.bbcode.js | 10372 ++++++++++++++++ public/assets/development/jquery.sceditor.js | 7637 ++++++++++++ .../development/jquery.sceditor.xhtml.js | 8885 +++++++++++++ public/assets/development/plugins/autosave.js | 105 + .../assets/development/plugins/autoyoutube.js | 94 + public/assets/development/plugins/dragdrop.js | 222 + public/assets/development/plugins/format.js | 127 + .../assets/development/plugins/plaintext.js | 59 + .../development/plugins/strictbbcode.js | 21 + public/assets/development/plugins/undo.js | 187 + public/assets/development/plugins/v1compat.js | 97 + public/assets/development/sceditor.js | 7571 +++++++++++ .../development/themes/content/default.css | 85 + public/assets/development/themes/default.css | 530 + .../assets/development/themes/defaultdark.css | 548 + .../assets/development/themes/famfamfam.png | Bin 0 -> 4583 bytes public/assets/development/themes/modern.css | 604 + .../development/themes/office-toolbar.css | 596 + public/assets/development/themes/office.css | 618 + public/assets/development/themes/square.css | 619 + public/assets/fonts/ionicons.eot | Bin 120724 -> 0 bytes public/assets/fonts/ionicons.svg | 2230 ---- public/assets/fonts/ionicons.ttf | Bin 188508 -> 0 bytes public/assets/fonts/ionicons.woff | Bin 67904 -> 0 bytes public/assets/fonts/wysibbiconfont-wb.eot | Bin 0 -> 4057 bytes public/assets/fonts/wysibbiconfont-wb.ttf | Bin 0 -> 6752 bytes public/assets/fonts/wysibbiconfont-wb.woff | Bin 0 -> 4732 bytes public/assets/img/emoticons/alien.png | Bin 0 -> 756 bytes public/assets/img/emoticons/angel.png | Bin 0 -> 1182 bytes public/assets/img/emoticons/angry.png | Bin 0 -> 781 bytes public/assets/img/emoticons/blink.png | Bin 0 -> 972 bytes public/assets/img/emoticons/blush.png | Bin 0 -> 865 bytes public/assets/img/emoticons/cheerful.png | Bin 0 -> 753 bytes public/assets/img/emoticons/cool.png | Bin 0 -> 965 bytes public/assets/img/emoticons/credits.txt | 9 + public/assets/img/emoticons/cwy.png | Bin 0 -> 877 bytes public/assets/img/emoticons/devil.png | Bin 0 -> 1012 bytes public/assets/img/emoticons/dizzy.png | Bin 0 -> 991 bytes public/assets/img/emoticons/ermm.png | Bin 0 -> 983 bytes public/assets/img/emoticons/face.png | Bin 0 -> 793 bytes public/assets/img/emoticons/getlost.png | Bin 0 -> 792 bytes public/assets/img/emoticons/grin.png | Bin 0 -> 867 bytes public/assets/img/emoticons/happy.png | Bin 0 -> 792 bytes public/assets/img/emoticons/heart.png | Bin 0 -> 572 bytes public/assets/img/emoticons/kissing.png | Bin 0 -> 793 bytes public/assets/img/emoticons/laughing.png | Bin 0 -> 912 bytes public/assets/img/emoticons/ninja.png | Bin 0 -> 694 bytes public/assets/img/emoticons/pinch.png | Bin 0 -> 804 bytes public/assets/img/emoticons/pouty.png | Bin 0 -> 799 bytes public/assets/img/emoticons/sad.png | Bin 0 -> 789 bytes public/assets/img/emoticons/shocked.png | Bin 0 -> 780 bytes public/assets/img/emoticons/sick.png | Bin 0 -> 783 bytes public/assets/img/emoticons/sideways.png | Bin 0 -> 788 bytes public/assets/img/emoticons/silly.png | Bin 0 -> 930 bytes public/assets/img/emoticons/sleeping.png | Bin 0 -> 1039 bytes public/assets/img/emoticons/smile.png | Bin 0 -> 983 bytes public/assets/img/emoticons/tongue.png | Bin 0 -> 981 bytes public/assets/img/emoticons/unsure.png | Bin 0 -> 763 bytes public/assets/img/emoticons/w00t.png | Bin 0 -> 718 bytes public/assets/img/emoticons/wassat.png | Bin 0 -> 810 bytes public/assets/img/emoticons/whistling.png | Bin 0 -> 1072 bytes public/assets/img/emoticons/wink.png | Bin 0 -> 791 bytes public/assets/img/emoticons/wub.png | Bin 0 -> 1010 bytes public/assets/img/logo-lg-white.png | Bin 20646 -> 0 bytes public/assets/js/jquery.wysibb.min.js | 5 + public/assets/languages/ar.js | 68 + public/assets/languages/ca.js | 68 + public/assets/languages/cn.js | 68 + public/assets/languages/cs.js | 71 + public/assets/languages/de.js | 59 + public/assets/languages/el.js | 68 + public/assets/languages/en-US.js | 7 + public/assets/languages/en.js | 12 + public/assets/languages/es.js | 68 + public/assets/languages/et.js | 57 + public/assets/languages/fa.js | 69 + public/assets/languages/fr.js | 70 + public/assets/languages/gl.js | 68 + public/assets/languages/hu.js | 69 + public/assets/languages/id.js | 68 + public/assets/languages/it.js | 72 + public/assets/languages/ja.js | 71 + public/assets/languages/lt.js | 68 + public/assets/languages/nb.js | 70 + public/assets/languages/nl.js | 57 + public/assets/languages/pl.js | 68 + public/assets/languages/pt-BR.js | 67 + public/assets/languages/pt.js | 69 + public/assets/languages/ru.js | 57 + public/assets/languages/sv.js | 58 + public/assets/languages/template.js | 75 + public/assets/languages/tr.js | 66 + public/assets/languages/tw.js | 68 + public/assets/languages/uk.js | 57 + public/assets/languages/vi.js | 68 + public/assets/minified/formats/bbcode.js | 3 + public/assets/minified/formats/xhtml.js | 3 + public/assets/minified/icons/material.js | 3 + public/assets/minified/icons/monocons.js | 3 + .../minified/jquery.sceditor.bbcode.min.js | 3 + public/assets/minified/jquery.sceditor.min.js | 3 + .../minified/jquery.sceditor.xhtml.min.js | 3 + public/assets/minified/plugins/autosave.js | 3 + public/assets/minified/plugins/autoyoutube.js | 3 + public/assets/minified/plugins/dragdrop.js | 3 + public/assets/minified/plugins/format.js | 3 + public/assets/minified/plugins/plaintext.js | 3 + .../assets/minified/plugins/strictbbcode.js | 2 + public/assets/minified/plugins/undo.js | 3 + public/assets/minified/plugins/v1compat.js | 3 + public/assets/minified/sceditor.min.js | 3 + .../minified/themes/content/default.min.css | 1 + public/assets/minified/themes/default.min.css | 5 + .../minified/themes/default_backup.min.css | 1 + .../minified/themes/defaultdark.min.css | 1 + public/assets/minified/themes/famfamfam.png | Bin 0 -> 4583 bytes public/assets/minified/themes/modern.min.css | 1 + .../minified/themes/office-toolbar.min.css | 1 + public/assets/minified/themes/office.min.css | 1 + public/assets/minified/themes/square.min.css | 1 + resources/views.zip | Bin 0 -> 35185 bytes resources/views/2fa/authenticate.blade.php | 2 +- resources/views/control.blade.php | 55 + resources/views/emails/change.blade.php | 13 + resources/views/home.blade.php | 26 +- resources/views/layouts/admin.blade.php | 44 +- resources/views/layouts/main.blade.php | 27 + resources/views/layouts/profile.blade.php | 22 + resources/views/openaudiomc.blade.php | 25 + resources/views/order.blade.php | 27 +- resources/views/panel/home.blade.php | 75 +- resources/views/panel/message.blade.php | 72 + resources/views/panel/show/create.blade.php | 100 + resources/views/panel/show/edit.blade.php | 77 + resources/views/panel/show/index.blade.php | 112 + resources/views/panel/show/info.blade.php | 29 + resources/views/panel/shows/create.blade.php | 107 + resources/views/panel/shows/index.blade.php | 108 + resources/views/panel/shows/info.blade.php | 28 + resources/views/panel/ums/edit.blade.php | 102 +- resources/views/panel/ums/index.blade.php | 106 +- resources/views/panel/ums/info.blade.php | 54 +- resources/views/photo.blade.php | 26 +- resources/views/profile/change.blade.php | 23 +- resources/views/profile/home.blade.php | 23 +- resources/views/profile/security.blade.php | 25 +- resources/views/ridecount.blade.php | 34 +- resources/views/show.blade.php | 26 +- resources/views/status.blade.php | 28 +- routes/web.php | 42 +- 196 files changed, 51167 insertions(+), 3010 deletions(-) create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/commandlinetools/Laravel_29_09_20_08_53.xml create mode 100644 .idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd create mode 100644 app/Http/Controllers/ControlController.php create mode 100644 app/Http/Controllers/OpenAudioMCController.php create mode 100644 app/Http/Controllers/Panel/MessageController.php create mode 100644 app/Http/Controllers/Panel/ShowController.php create mode 100644 app/Http/Controllers/Panel/ShowsController.php create mode 100644 app/Http/Middleware/AdminOnly.php create mode 100644 app/Mail/EmailChange.php delete mode 100644 app/Notifications/SendMailChange.php create mode 100644 app/ShowDate.php create mode 100644 public/assets/control.js delete mode 100644 public/assets/css/ionicons.min.css create mode 100644 public/assets/css/panel_default.min.css create mode 100644 public/assets/css/skin_default.css create mode 100644 public/assets/css/style_default.css create mode 100644 public/assets/development/formats/bbcode.js create mode 100644 public/assets/development/formats/xhtml.js create mode 100644 public/assets/development/icons/material.js create mode 100644 public/assets/development/icons/monocons.js create mode 100644 public/assets/development/jquery.sceditor.bbcode.js create mode 100644 public/assets/development/jquery.sceditor.js create mode 100644 public/assets/development/jquery.sceditor.xhtml.js create mode 100644 public/assets/development/plugins/autosave.js create mode 100644 public/assets/development/plugins/autoyoutube.js create mode 100644 public/assets/development/plugins/dragdrop.js create mode 100644 public/assets/development/plugins/format.js create mode 100644 public/assets/development/plugins/plaintext.js create mode 100644 public/assets/development/plugins/strictbbcode.js create mode 100644 public/assets/development/plugins/undo.js create mode 100644 public/assets/development/plugins/v1compat.js create mode 100644 public/assets/development/sceditor.js create mode 100644 public/assets/development/themes/content/default.css create mode 100644 public/assets/development/themes/default.css create mode 100644 public/assets/development/themes/defaultdark.css create mode 100644 public/assets/development/themes/famfamfam.png create mode 100644 public/assets/development/themes/modern.css create mode 100644 public/assets/development/themes/office-toolbar.css create mode 100644 public/assets/development/themes/office.css create mode 100644 public/assets/development/themes/square.css delete mode 100644 public/assets/fonts/ionicons.eot delete mode 100644 public/assets/fonts/ionicons.svg delete mode 100644 public/assets/fonts/ionicons.ttf delete mode 100644 public/assets/fonts/ionicons.woff create mode 100644 public/assets/fonts/wysibbiconfont-wb.eot create mode 100644 public/assets/fonts/wysibbiconfont-wb.ttf create mode 100644 public/assets/fonts/wysibbiconfont-wb.woff create mode 100644 public/assets/img/emoticons/alien.png create mode 100644 public/assets/img/emoticons/angel.png create mode 100644 public/assets/img/emoticons/angry.png create mode 100644 public/assets/img/emoticons/blink.png create mode 100644 public/assets/img/emoticons/blush.png create mode 100644 public/assets/img/emoticons/cheerful.png create mode 100644 public/assets/img/emoticons/cool.png create mode 100644 public/assets/img/emoticons/credits.txt create mode 100644 public/assets/img/emoticons/cwy.png create mode 100644 public/assets/img/emoticons/devil.png create mode 100644 public/assets/img/emoticons/dizzy.png create mode 100644 public/assets/img/emoticons/ermm.png create mode 100644 public/assets/img/emoticons/face.png create mode 100644 public/assets/img/emoticons/getlost.png create mode 100644 public/assets/img/emoticons/grin.png create mode 100644 public/assets/img/emoticons/happy.png create mode 100644 public/assets/img/emoticons/heart.png create mode 100644 public/assets/img/emoticons/kissing.png create mode 100644 public/assets/img/emoticons/laughing.png create mode 100644 public/assets/img/emoticons/ninja.png create mode 100644 public/assets/img/emoticons/pinch.png create mode 100644 public/assets/img/emoticons/pouty.png create mode 100644 public/assets/img/emoticons/sad.png create mode 100644 public/assets/img/emoticons/shocked.png create mode 100644 public/assets/img/emoticons/sick.png create mode 100644 public/assets/img/emoticons/sideways.png create mode 100644 public/assets/img/emoticons/silly.png create mode 100644 public/assets/img/emoticons/sleeping.png create mode 100644 public/assets/img/emoticons/smile.png create mode 100644 public/assets/img/emoticons/tongue.png create mode 100644 public/assets/img/emoticons/unsure.png create mode 100644 public/assets/img/emoticons/w00t.png create mode 100644 public/assets/img/emoticons/wassat.png create mode 100644 public/assets/img/emoticons/whistling.png create mode 100644 public/assets/img/emoticons/wink.png create mode 100644 public/assets/img/emoticons/wub.png delete mode 100644 public/assets/img/logo-lg-white.png create mode 100644 public/assets/js/jquery.wysibb.min.js create mode 100644 public/assets/languages/ar.js create mode 100644 public/assets/languages/ca.js create mode 100644 public/assets/languages/cn.js create mode 100644 public/assets/languages/cs.js create mode 100644 public/assets/languages/de.js create mode 100644 public/assets/languages/el.js create mode 100644 public/assets/languages/en-US.js create mode 100644 public/assets/languages/en.js create mode 100644 public/assets/languages/es.js create mode 100644 public/assets/languages/et.js create mode 100644 public/assets/languages/fa.js create mode 100644 public/assets/languages/fr.js create mode 100644 public/assets/languages/gl.js create mode 100644 public/assets/languages/hu.js create mode 100644 public/assets/languages/id.js create mode 100644 public/assets/languages/it.js create mode 100644 public/assets/languages/ja.js create mode 100644 public/assets/languages/lt.js create mode 100644 public/assets/languages/nb.js create mode 100644 public/assets/languages/nl.js create mode 100644 public/assets/languages/pl.js create mode 100644 public/assets/languages/pt-BR.js create mode 100644 public/assets/languages/pt.js create mode 100644 public/assets/languages/ru.js create mode 100644 public/assets/languages/sv.js create mode 100644 public/assets/languages/template.js create mode 100644 public/assets/languages/tr.js create mode 100644 public/assets/languages/tw.js create mode 100644 public/assets/languages/uk.js create mode 100644 public/assets/languages/vi.js create mode 100644 public/assets/minified/formats/bbcode.js create mode 100644 public/assets/minified/formats/xhtml.js create mode 100644 public/assets/minified/icons/material.js create mode 100644 public/assets/minified/icons/monocons.js create mode 100644 public/assets/minified/jquery.sceditor.bbcode.min.js create mode 100644 public/assets/minified/jquery.sceditor.min.js create mode 100644 public/assets/minified/jquery.sceditor.xhtml.min.js create mode 100644 public/assets/minified/plugins/autosave.js create mode 100644 public/assets/minified/plugins/autoyoutube.js create mode 100644 public/assets/minified/plugins/dragdrop.js create mode 100644 public/assets/minified/plugins/format.js create mode 100644 public/assets/minified/plugins/plaintext.js create mode 100644 public/assets/minified/plugins/strictbbcode.js create mode 100644 public/assets/minified/plugins/undo.js create mode 100644 public/assets/minified/plugins/v1compat.js create mode 100644 public/assets/minified/sceditor.min.js create mode 100644 public/assets/minified/themes/content/default.min.css create mode 100644 public/assets/minified/themes/default.min.css create mode 100644 public/assets/minified/themes/default_backup.min.css create mode 100644 public/assets/minified/themes/defaultdark.min.css create mode 100644 public/assets/minified/themes/famfamfam.png create mode 100644 public/assets/minified/themes/modern.min.css create mode 100644 public/assets/minified/themes/office-toolbar.min.css create mode 100644 public/assets/minified/themes/office.min.css create mode 100644 public/assets/minified/themes/square.min.css create mode 100644 resources/views.zip create mode 100644 resources/views/control.blade.php create mode 100644 resources/views/emails/change.blade.php create mode 100644 resources/views/layouts/main.blade.php create mode 100644 resources/views/layouts/profile.blade.php create mode 100644 resources/views/openaudiomc.blade.php create mode 100644 resources/views/panel/message.blade.php create mode 100644 resources/views/panel/show/create.blade.php create mode 100644 resources/views/panel/show/edit.blade.php create mode 100644 resources/views/panel/show/index.blade.php create mode 100644 resources/views/panel/show/info.blade.php create mode 100644 resources/views/panel/shows/create.blade.php create mode 100644 resources/views/panel/shows/index.blade.php create mode 100644 resources/views/panel/shows/info.blade.php diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/commandlinetools/Laravel_29_09_20_08_53.xml b/.idea/commandlinetools/Laravel_29_09_20_08_53.xml new file mode 100644 index 0000000..639cdf3 --- /dev/null +++ b/.idea/commandlinetools/Laravel_29_09_20_08_53.xml @@ -0,0 +1,2771 @@ + + + + + clear-compiled +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + down +
Options:
--messageThe message for the maintenance mode
--retryThe number of seconds after which the request may be retried
--allowIP or networks allowed to access the application while in maintenance mode
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + +
+ + env +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + help + help command displays help for a given command:

php D:/Temp Data Save/ThemeParkPanel Laravel/artisan help list

You can also output the help in other formats by using the --format option:

php D:/Temp Data Save/ThemeParkPanel Laravel/artisan help --format=xml list

To display the list of available commands, please use the list command.

Options:
--formatThe output format (txt, xml, json, or md)
--rawTo output raw command help
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ command_name[=null] + + + + + + + + + + + + +
+ + list + list command lists all commands:

php D:/Temp Data Save/ThemeParkPanel Laravel/artisan list

You can also display the commands for a specific namespace:

php D:/Temp Data Save/ThemeParkPanel Laravel/artisan list test

You can also output the information in other formats by using the --format option:

php D:/Temp Data Save/ThemeParkPanel Laravel/artisan list --format=xml

It's also possible to get raw list of commands (useful for embedding command runner):

php D:/Temp Data Save/ThemeParkPanel Laravel/artisan list --raw

Options:
--rawTo output raw command list
--formatThe output format (txt, xml, json, or md)

]]>
+ namespace[=null] + + + + +
+ + migrate +
Options:
--databaseThe database connection to use
--forceForce the operation to run when in production
--pathThe path(s) to the migrations files to be executed
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--pretendDump the SQL queries that would be run
--seedIndicates if the seed task should be re-run
--stepForce the migrations to be run so they can be rolled back individually
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + + + + +
+ + optimize +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + preset +
Options:
--optionPass an option to the preset command
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ type + + + + + + + + + + + +
+ + serve +
Options:
--hostThe host address to serve the application on
--portThe port to serve the application on
--triesThe max number of ports to attempt to serve from
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + +
+ + tinker +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ include[=null] + + + + + + + + + + +
+ + ui +
Options:
--authInstall authentication UI scaffolding
--optionPass an option to the preset command
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ type + + + + + + + + + + + + +
+ + up +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + auth:clear-resets +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name[=null] + + + + + + + + + + +
+ + cache:clear +
Options:
--tagsThe cache tags you would like to clear
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ store[=null] + + + + + + + + + + + +
+ + cache:forget +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ key store[=null] + + + + + + + + + + +
+ + cache:table +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + config:cache +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + config:clear +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + db:seed +
Options:
--classThe class name of the root seeder
--databaseThe database connection to seed
--forceForce the operation to run when in production
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + +
+ + db:wipe +
Options:
--databaseThe database connection to use
--drop-viewsDrop all tables and views
--drop-typesDrop all tables and types (Postgres only)
--forceForce the operation to run when in production
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + +
+ + event:cache +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + event:clear +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + event:generate +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + event:list +
Options:
--eventFilter the events by name
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + +
+ + flare:test +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + geoip:clear +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + geoip:update +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + key:generate +
Options:
--showDisplay the key instead of modifying files
--forceForce the operation to run when in production
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + +
+ + make:channel +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:command +
Options:
--commandThe terminal command that should be assigned
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + make:controller +
Options:
--model(-m)Generate a resource controller for the given model.
--resource(-r)Generate a resource controller class.
--invokable(-i)Generate a single method, invokable controller class.
--parent(-p)Generate a nested resource controller class.
--apiExclude the create and edit methods from the controller.
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + + + + +
+ + make:enum +
Options:
--methodThe method name that should be added to the enum
--valueThe value that should be added to the enum
--formatterThe formatter to use for the value to method conversion (snake, const, studly, camel)
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + + +
+ + make:event +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:exception +
Options:
--renderCreate the exception with an empty render method
--reportCreate the exception with an empty report method
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + +
+ + make:factory +
Options:
--model(-m)The name of the model
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + make:job +
Options:
--syncIndicates that job should be synchronous
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + make:listener +
Options:
--event(-e)The event class being listened for
--queuedIndicates the event listener should be queued
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + +
+ + make:mail +
Options:
--force(-f)Create the class even if the mailable already exists
--markdown(-m)Create a new Markdown template for the mailable
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + +
+ + make:middleware +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:migration +
Options:
--createThe table to be created
--tableThe table to migrate
--pathThe location where the migration file should be created
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--fullpathOutput the full path of the migration
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + + + + +
+ + make:model +
Options:
--all(-a)Generate a migration, factory, and resource controller for the model
--controller(-c)Create a new controller for the model
--factory(-f)Create a new factory for the model
--forceCreate the class even if the model already exists
--migration(-m)Create a new migration file for the model
--pivot(-p)Indicates if the generated model should be a custom intermediate table model
--resource(-r)Indicates if the generated controller should be a resource controller
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + + + + + + +
+ + make:notification +
Options:
--force(-f)Create the class even if the notification already exists
--markdown(-m)Create a new Markdown template for the notification
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + + +
+ + make:observer +
Options:
--model(-m)The model that the observer applies to.
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + make:policy +
Options:
--model(-m)The model that the policy applies to
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + make:provider +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:request +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:resource +
Options:
--collection(-c)Create a resource collection
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + make:rule +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:seeder +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + +
+ + make:test +
Options:
--unitCreate a unit test
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ name + + + + + + + + + + + +
+ + migrate:fresh +
Options:
--databaseThe database connection to use
--drop-viewsDrop all tables and views
--drop-typesDrop all tables and types (Postgres only)
--forceForce the operation to run when in production
--pathThe path to the migrations files to be executed
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--seedIndicates if the seed task should be re-run
--seederThe class name of the root seeder
--stepForce the migrations to be run so they can be rolled back individually
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + + + + + + +
+ + migrate:install +
Options:
--databaseThe database connection to use
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + +
+ + migrate:refresh +
Options:
--databaseThe database connection to use
--forceForce the operation to run when in production
--pathThe path to the migrations files to be executed
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--seedIndicates if the seed task should be re-run
--seederThe class name of the root seeder
--stepThe number of migrations to be reverted & re-run
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + + + + +
+ + migrate:reset +
Options:
--databaseThe database connection to use
--forceForce the operation to run when in production
--pathThe path(s) to the migrations files to be executed
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--pretendDump the SQL queries that would be run
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + + +
+ + migrate:rollback +
Options:
--databaseThe database connection to use
--forceForce the operation to run when in production
--pathThe path(s) to the migrations files to be executed
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--pretendDump the SQL queries that would be run
--stepThe number of migrations to be reverted
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + + + +
+ + migrate:status +
Options:
--databaseThe database connection to use
--pathThe path(s) to the migrations files to use
--realpathIndicate any provided migration file paths are pre-resolved absolute paths
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + +
+ + notifications:table +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + optimize:clear +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + package:discover +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + queue:failed +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + queue:failed-table +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + queue:flush +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + queue:forget +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ id + + + + + + + + + + +
+ + queue:listen +
Options:
--delayThe number of seconds to delay failed jobs
--forceForce the worker to run even in maintenance mode
--memoryThe memory limit in megabytes
--queueThe queue to listen on
--sleepNumber of seconds to sleep when no job is available
--timeoutThe number of seconds a child process can run
--triesNumber of times to attempt a job before logging it failed
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ connection[=null] + + + + + + + + + + + + + + + + + +
+ + queue:restart +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + queue:retry +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ id + + + + + + + + + + +
+ + queue:table +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + queue:work +
Options:
--queueThe names of the queues to work
--daemonRun the worker in daemon mode (Deprecated)
--onceOnly process the next job on the queue
--stop-when-emptyStop when the queue is empty
--delayThe number of seconds to delay failed jobs
--forceForce the worker to run even in maintenance mode
--memoryThe memory limit in megabytes
--sleepNumber of seconds to sleep when no job is available
--timeoutThe number of seconds a child process can run
--triesNumber of times to attempt a job before logging it failed
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ connection[=null] + + + + + + + + + + + + + + + + + + + + +
+ + route:cache +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + route:clear +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + route:list +
Options:
--columnsColumns to include in the route table
--compact(-c)Only show method, URI and action columns
--jsonOutput the route list as JSON
--methodFilter the routes by method
--nameFilter the routes by name
--pathFilter the routes by path
--reverse(-r)Reverse the ordering of the routes
--sortThe column (domain, method, uri, name, action, middleware) to sort by
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + + + + + +
+ + schedule:finish +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ id + + + + + + + + + + +
+ + schedule:run +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + session:table +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + storage:link +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + ui:auth +
Options:
--viewsOnly scaffold the authentication views
--forceOverwrite existing views by default
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ type[=null] + + + + + + + + + + + + +
+ + vendor:publish +
Options:
--forceOverwrite any existing files
--allPublish assets for all service providers without prompt
--providerThe service provider that has assets you want to publish
--tagOne or many tags that have assets you want to publish
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + + + + + +
+ + view:cache +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+ + view:clear +
Options:
--help(-h)Display this help message
--quiet(-q)Do not output any message
--verbose(-v)Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
--version(-V)Display this application version
--ansiForce ANSI output
--no-ansiDisable ANSI output
--no-interaction(-n)Do not ask any interactive question
--envThe environment the command should run under

]]>
+ + + + + + + + + + +
+
+ diff --git a/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd b/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd new file mode 100644 index 0000000..f2efc6d --- /dev/null +++ b/.idea/commandlinetools/schemas/frameworkDescriptionVersion1.1.4.xsd @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/Cache/Cache.php b/app/Cache/Cache.php index d52e28d..3c525cf 100644 --- a/app/Cache/Cache.php +++ b/app/Cache/Cache.php @@ -3,92 +3,161 @@ namespace App\Cache; class Cache { + /** + * Get Username from API or cache + * + * @param $uuid + * @return bool|mixed The username if success or the UUID if failed + */ public static function getUsername($uuid) { - if(file_exists(storage_path('app/uuid/'.$uuid.'.json'))) { + if (file_exists(storage_path('app/uuid/'.$uuid.'.json'))) { $json = file_get_contents(storage_path('app/uuid/'.$uuid.'.json')); $json = json_decode($json, true); - if((time() - strtotime($json['time'])) > 3600) { - $json = file_get_contents('https://api.mojang.com/users/profiles/'.$uuid.'./names'); - if(empty($json)) { - $json = file_get_contents(storage_path('app/uuid/'.$uuid.'.json')); - $json = json_decode($json, true); + if ((time() - strtotime($json['time'])) > 3600) { + $username = self::getUsernameUncached($uuid); + if ($username == false) { + return $json['name']; + } else { + $json = []; + $json['id'] = $uuid; + $json['name'] = $username; + self::saveJson($json); return $json['name']; } - - $json = json_decode($json, true); - if(isset($json['error'])) { - $json = file_get_contents(storage_path('app/uuid/'.$uuid.'.json')); - $json = json_decode($json, true); - return $json['name']; - } - - $name = $json[count($json) -1]['name']; - $json = []; - $json['id'] = $uuid; - $json['name'] = $name; - self::saveJson($json); - return $json['name']; } else { return $json['name']; } } else { - $json = file_get_contents('https://api.mojang.com/user/profiles/'.$uuid.'/names'); - if(empty($json)) + $username = self::getUsernameUncached($uuid); + if ($username === $uuid) { return $uuid; - - $json = json_decode($json, true); - if(isset($json['error'])) - return $uuid; - - $name = $json[count($json) -1]['name']; - $json = []; - $json['id'] = $uuid; - $json['name'] = $name; - self::saveJson($json); - return $json['name']; + } else { + $json = []; + $json['id'] = $uuid; + $json['name'] = $username; + self::saveJson($json); + return $json['name']; + } } } + /** + * Get UUID from API or cache + * + * @param $username + * @return bool|mixed The UUID if success or the username if failed + */ public static function getUUID($username) { foreach(glob(storage_path('app/uuid/*')) as $file) { $json = file_get_contents($file); $json = json_decode($json, true); - if($json['name'] !== $username) - continue; - if((time() - strtotime($json['time'])) > 3600) { - $json = file_get_contents('https://api.mojang.com/users/profiles/minecraft/'.$username); - if(empty($json)) { + if ($json['name'] !== $username) continue; + + if ((time() - strtotime($json['time'])) > 3600) { + $uuid = self::getUUIDUncached($username); + if ($uuid == false) { unlink(storage_path('app/uuid/'.$file)); - return $username; + return $json['name']; + } else { + $json = []; + $json['id'] = $uuid; + $json['name'] = $username; + self::saveJson($json); + return $json['id']; } - - $json = json_decode($json, true); - if(isset($json['error'])) { - unlink(storage_path('app/uuid/'.$file)); - return $username; - } - - self::saveJson($json); - return $json['id']; } else { return $json['id']; } } - $json = file_get_contents('https://api.mojang.com/users/profiles/minecraft/'.$username); - if(empty($json)) + $uuid = self::getUUIDUncached($username); + if ($uuid == false) { return $username; - - $json = json_decode($json, true); - if(isset($json['error'])) - return $username; - - self::saveJson($json); - return $json['id']; + } else { + $json = []; + $json['id'] = $uuid; + $json['name'] = $username; + self::saveJson($json); + return $json['id']; + } } - public static function saveJson($json) { + /** + * Get the UUID by the username + * + * @param $username + * @return bool|mixed The UUID without dashes, or false if failed + */ + private static function getUUIDUncached($username) { + $profile = self::getProfile($username); + if (is_array($profile) and isset($profile['id'])) + return $profile['id']; + + return false; + } + + /** + * Get the profile (username & UUID) from the username + * + * @uses http://wiki.vg/Mojang_API#Username_-.3E_UUID_at_time + * + * @param $username + * @return bool|mixed Array with ID and name, or false if failed + */ + private static function getProfile($username) { + if (self::isValidUsername($username)) { + $json = file_get_contents('https://api.mojang.com/users/profiles/minecraft/' . $username); + if (!empty($json)) { + $data = json_decode($json, true); + if (is_array($data) and !empty($data)) { + return $data; + } + } + } + return false; + } + + /** + * Get the username from the UUID + * + * @uses http://wiki.vg/Mojang_API#UUID_-.3E_Name_history + * + * @param $uuid + * @return bool|mixed Username, or false if failed + */ + private static function getUsernameUncached($uuid) { + if (is_string($uuid)) { + $json = file_get_contents('https://api.mojang.com/user/profiles/' . $uuid . '/names'); + if (!empty($json)) { + $data = json_decode($json, true); + if (!empty($data) and is_array($data)) { + $last = array_pop($data); + if (is_array($last) and isset($last['name'])) { + return $last['name']; + } + } + } + } + return false; + } + + /** + * Check if the username is correct. + * + * @param $username + * @return bool Valid or not + */ + private static function isValidUsername($username) { + return is_string($username) and strlen($username) >= 2 and strlen($username) <= 16 and ctype_alnum(str_replace('_', '', $username)); + } + + /** + * Save the JSON to a file + * + * @param $json + */ + private static function saveJson($json) { $array = [ 'id' => $json['id'], 'name' => $json['name'], diff --git a/app/ChangeEmail.php b/app/ChangeEmail.php index f192605..40ffa70 100644 --- a/app/ChangeEmail.php +++ b/app/ChangeEmail.php @@ -2,7 +2,6 @@ namespace App; -use App\Notifications\SendMailChange; use Illuminate\Database\Eloquent\Model; class ChangeEmail extends Model @@ -19,9 +18,4 @@ class ChangeEmail extends Model 'user_id', 'email', 'token' ]; - public function sendMail() { - $user = User::findOrFail($this->user_id); - $user->notify(new SendMailChange($this)); - } - } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 78b1971..6dcc7db 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -48,6 +48,16 @@ class LoginController extends Controller return 'uuid'; } + public function showLoginForm() + { + $urlPrevious = url()->previous(); + $urlBase = url()->to('/'); + if(($urlPrevious != $urlBase . '/login') && (substr($urlPrevious, 0, strlen($urlBase)) === $urlBase)) + session()->put('url.intended', $urlPrevious); + + return view('auth.login'); + } + public function login(Request $request) { $this->validateLogin($request); @@ -67,15 +77,15 @@ class LoginController extends Controller return redirect()->intended('home'); } else { $this->incrementLoginAttempts($request); - return response()->json([ - 'error' => 'This account is not activated.' - ], 401); + return view('auth.login')->withErrors([ + 'error' => 'Wrong credentials.' + ]); } } else { $this->incrementLoginAttempts($request); - return response()->json([ - 'error' => 'Credentials do not match our database.' - ], 401); + return view('auth.login')->withErrors([ + 'error' => 'Wrong credentials.' + ]); } } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 9f59ef5..cf64389 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -68,7 +68,7 @@ class RegisterController extends Controller protected function create(array $data) { return User::create([ - 'uuid' => '', Cache::getUUID($data['username']), + 'uuid' => Cache::getUUID($data['username']), 'email' => $data['email'], 'password' => bcrypt($data['password']) ]); diff --git a/app/Http/Controllers/ControlController.php b/app/Http/Controllers/ControlController.php new file mode 100644 index 0000000..0808e10 --- /dev/null +++ b/app/Http/Controllers/ControlController.php @@ -0,0 +1,45 @@ +middleware(['auth', 'verified', '2fa']); + } + + /** + * Show the application dashboard. + * + * @param $attraction_id + * @param $pin + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index($attraction_id, $pin) + { + if(!preg_match('/^([0-9]){9}$/', $pin)) + return Redirect::route('status'); + + $data = DB::table('attraction')->select('name')->where('id', '=', $attraction_id)->first(); + if(empty($data)) + return Redirect::route('status'); + + return view('control')->with([ + 'attraction_id' => $attraction_id, + 'attraction_name' => MinecraftColor::stripColor( $data->name), + 'pin' => $pin + ]); + } + +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index d399e23..f232d72 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Redirect; class HomeController extends Controller { @@ -47,4 +48,9 @@ class HomeController extends Controller ]); } + public function store() + { + return Redirect::to(env('STORE_URL', 'https://sbdplugins.nl')); + } + } diff --git a/app/Http/Controllers/OpenAudioMCController.php b/app/Http/Controllers/OpenAudioMCController.php new file mode 100644 index 0000000..10a0f37 --- /dev/null +++ b/app/Http/Controllers/OpenAudioMCController.php @@ -0,0 +1,70 @@ +middleware(['auth', 'verified', '2fa']); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + $url = env('OPENAUDIOMC_URL', ''); + if(empty($url)) + return view('openaudiomc')->with(['type' => 1]); + + if(!filter_var($url, FILTER_VALIDATE_URL)) + return view('openaudiomc')->with(['type' => 1]); + + $key = explode('/', $url); + $key = $key[count($key) - 1]; + if(!preg_match('/^([a-zA-Z0-9]{8})\-([a-zA-Z0-9]{4})\-([a-zA-Z0-9]{4})\-([a-zA-Z0-9]{4})\-([a-zA-Z0-9]{12})$/', $key)) + return view('openaudiomc')->with(['type' => 1]); + + $url = str_replace('%UUID%', Auth::user()->fixedUUID(), $url); + $result = file_get_contents($url); + if(!$this->isJson($result)) + return view('openaudiomc')->with(['type' => 1]); + + $json = json_decode($result); + if(isset($json->errors) && !empty($json->errors)) + return view('openaudiomc')->with(['type' => 2]); + + if(!isset($json->response)) + return view('openaudiomc')->with(['type' => 2]); + + $response = $json->response; + if(!isset($response->isConnected) || !isset($response->sessionUrl)) + return view('openaudiomc')->with(['type' => 2]); + + if($response->isConnected) + return view('openaudiomc')->with(['type' => 3]); + + $link = $json->response->sessionUrl; + header('Location: '.$link); + exit; + } + + private function isJson($string) { + if(empty($string)) + return false; + + json_decode($string); + return (json_last_error() == JSON_ERROR_NONE); + } + +} diff --git a/app/Http/Controllers/Panel/HomeController.php b/app/Http/Controllers/Panel/HomeController.php index d5288e3..fdfbc58 100644 --- a/app/Http/Controllers/Panel/HomeController.php +++ b/app/Http/Controllers/Panel/HomeController.php @@ -3,6 +3,9 @@ namespace App\Http\Controllers\Panel; use App\Http\Controllers\Controller; +use App\Show; +use App\User; +use Illuminate\Support\Facades\DB; class HomeController extends Controller { @@ -13,7 +16,7 @@ class HomeController extends Controller */ public function __construct() { - $this->middleware(['auth', 'verified', '2fa']); + $this->middleware(['auth', 'verified', '2fa', 'admin']); } /** @@ -23,8 +26,15 @@ class HomeController extends Controller */ public function index() { + $users = User::count(); + $regions = DB::table('region')->count(); + $attractions = DB::table('attraction')->where('type', '!=', 'GLOBAL')->count(); + $shows = Show::count(); return view('panel.home')->with([ - 'data' => '' + 'users' => $users, + 'regions' => $regions, + 'attractions' => $attractions, + 'shows' => $shows ]); } diff --git a/app/Http/Controllers/Panel/MessageController.php b/app/Http/Controllers/Panel/MessageController.php new file mode 100644 index 0000000..3d0b3a7 --- /dev/null +++ b/app/Http/Controllers/Panel/MessageController.php @@ -0,0 +1,63 @@ +middleware(['auth', 'verified', '2fa', 'admin']); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + return view('panel.message'); + } + + public function change(Request $request) { + $validator = Validator::make($request->all(), [ + 'message' => ['required', 'string'] + ]); + + if(!$validator->passes()) { + session()->flash('error', 'Incorrect message'); + return Redirect::back(); + } + + $message = Message::orderByDesc('id')->first(); + if(!empty($message)) + if($message->content === $request->get('message')) + return Redirect::back(); + + $message = Message::create([ + 'uuid' => Auth::user()->uuid, + 'content' => $request->get('message') + ]); + + if(empty($message)) { + session()->flash('error', 'Unable to create message'); + return Redirect::back(); + } + + session()->flash('success', 'Successfully posted message'); + return Redirect::back(); + } + +} diff --git a/app/Http/Controllers/Panel/ShowController.php b/app/Http/Controllers/Panel/ShowController.php new file mode 100644 index 0000000..d887c80 --- /dev/null +++ b/app/Http/Controllers/Panel/ShowController.php @@ -0,0 +1,143 @@ +middleware(['auth', 'verified', '2fa', 'admin']); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index($page = 1, $search = '') + { + $pages = empty($search) ? Show::count() : Show::whereRaw("UPPER(`title`) LIKE '%". strtoupper($search)."%'")->count(); + $pages = (int) ceil($pages/25); + if($pages < 1 && $page == 1) + $page = 1; + + if($page < 1 || ($pages > 0 && $page > $pages)) { + $array['page'] = $pages > 0 ? $pages : 1; + if(!empty($search) && $pages > 0) + $array['search'] = $search; + + return redirect()->route('panel.show', $array); + } + + $query = Show::select('id', 'title', 'price', 'vault_price', 'seats'); + if(!empty($search)) + $query->whereRaw("UPPER(`title`) LIKE '%". strtoupper($search)."%'"); + + $data = $query->get(); + return view('panel.show.index')->with([ + 'shows' => $data, + 'page' => $page, + 'pages' => $pages, + 'search' => $search + ]); + } + + public function add() { + return view('panel.show.create'); + } + + public function create(Request $request) { + $validator = Validator::make($request->all(), [ + 'title' => ['required', 'unique:shows,title', 'max:255'], + 'description' => ['required', 'string'], + 'price' => ['required', 'numeric', 'min:0.01'], + 'vault_price' => ['required', 'numeric', 'min:0.01'], + 'seats' => ['required', 'numeric', 'min:1'], + 'image' => ['required', 'string', 'max:255'], + ]); + + if(!$validator->passes()) + return Redirect::back()->withErrors($validator->errors()); + + $request->merge([ + 'price' => number_format($request->get('price'), 2), + 'vault_price' => number_format($request->get('vault_price'), 2) + ]); + + $show = Show::create($request->all()); + if(empty($show)) { + session()->flash('error', 'Unable to create a new Show'); + return Redirect::route('panel.ums'); + } + + session()->flash('success', 'Successfully created show.'); + return Redirect::route('panel.show'); + } + + public function info($id) { + return view('panel.show.info')->with([ + 'show' => Show::findOrFail($id) + ]); + } + + public function edit($id) { + return view('panel.show.edit')->with([ + 'show' => Show::findOrFail($id) + ]); + } + + public function update(Request $request) { + if(!$request->has('id')) + return Redirect::back(); + + $show = Show::findOrFail($request->get('id')); + $validator = Validator::make($request->all(), [ + 'description' => ['required', 'string'], + 'price' => ['required', 'numeric', 'min:0.01'], + 'vault_price' => ['required', 'numeric', 'min:0.01'], + 'image' => ['required', 'string', 'max:255'], + ]); + + if(!$validator->passes()) + return Redirect::back()->withErrors($validator->errors()); + + $show->description = $request->get('description'); + $show->price = number_format($request->get('price'), 2); + $show->vault_price = number_format($request->get('vault_price'), 2); + $show->image = $request->get('image'); + if($show->save()) { + session()->flash('success', 'Successfully edited show'); + return Redirect::route('panel.show'); + } + + session()->flash('error', 'Unable to edit show'); + return Redirect::back(); + } + + public function delete($id) { + $show = Show::findOrFail($id); + if($show->delete()) { + session()->flash('success', 'Successfully deleted show.'); + } else { + session()->flash('error', 'Unable to delete show.'); + } + + return Redirect::back(); + } + +} diff --git a/app/Http/Controllers/Panel/ShowsController.php b/app/Http/Controllers/Panel/ShowsController.php new file mode 100644 index 0000000..9d48713 --- /dev/null +++ b/app/Http/Controllers/Panel/ShowsController.php @@ -0,0 +1,125 @@ +middleware(['auth', 'verified', '2fa', 'admin']); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index($page = 1, $search = '') + { + $pages = empty($search) ? ShowDate::whereRaw('`show_dates`.`date` > CURDATE()')->count() : Show::join('shows', 'shows.id', '=', 'show_dates.show_id') + ->whereRaw('`show_dates`.`date` > CURDATE()') + ->whereRaw("UPPER(`title`) LIKE '%". strtoupper($search)."%'")->count(); + + $pages = (int) ceil($pages/25); + if($pages < 1 && $page == 1) + $page = 1; + + if($page < 1 || ($pages > 0 && $page > $pages)) { + $array['page'] = $pages > 0 ? $pages : 1; + if(!empty($search) && $pages > 0) + $array['search'] = $search; + + return redirect()->route('panel.shows', $array); + } + + $query = ShowDate::join('shows', 'shows.id', '=', 'show_dates.show_id') + ->whereRaw('`show_dates`.`date` > CURDATE()') + ->select('show_dates.id', 'shows.title', DB::raw('DATE_FORMAT(`show_dates`.`date`, "%d-%m-%Y %H:%i") AS `date`')); + + if(!empty($search)) + $query->whereRaw("UPPER(`shows.title`) LIKE '%". strtoupper($search)."%'"); + + $data = $query->get(); + return view('panel.shows.index')->with([ + 'dates' => $data, + 'page' => $page, + 'pages' => $pages, + 'search' => $search + ]); + } + + public function search(Request $request) { + if(!$request->has('searchText')) + return response()->json([]); + + $shows = Show::whereRaw("UPPER(`title`) LIKE '%". strtoupper($request->get('searchText'))."%'")->select('id', 'title')->get(); + return response()->json($shows->all()); + } + + public function add() { + return view('panel.shows.create'); + } + + public function create(Request $request) { + $validator = Validator::make($request->all(), [ + 'show_id' => ['required', 'numeric', 'exists:shows,id'], + 'date' => ['required', 'date'] + ]); + + if(!$validator->passes()) + return Redirect::back()->withErrors($validator->errors()); + + $request->merge([ + 'date' => date('Y-m-d H:i:s', strtotime($request->get('date'))) + ]); + + $date = ShowDate::create($request->all()); + if(empty($date)) { + session()->flash('error', 'Unable to create a new show date'); + return Redirect::route('panel.ums'); + } + + session()->flash('success', 'Successfully created show date.'); + return Redirect::route('panel.shows'); + } + + public function info($id) { + $date = ShowDate::findOrFail($id); + + $filled = DB::table('seats')->where('show_id', '=', $date->show_id)->where('date', '=', $date->date)->count(); + return view('panel.shows.info')->with([ + 'date' => $date, + 'show' => Show::findOrFail($date->show_id), + 'filled' => $filled + ]); + } + + public function delete($id) { + $show = ShowDate::findOrFail($id); + if($show->delete()) { + session()->flash('success', 'Successfully deleted show date.'); + } else { + session()->flash('error', 'Unable to delete show date.'); + } + + return Redirect::back(); + } + +} diff --git a/app/Http/Controllers/Panel/UMSController.php b/app/Http/Controllers/Panel/UMSController.php index 2316271..5ea7e11 100644 --- a/app/Http/Controllers/Panel/UMSController.php +++ b/app/Http/Controllers/Panel/UMSController.php @@ -3,7 +3,7 @@ namespace App\Http\Controllers\Panel; use App\ChangeEmail; use App\Http\Controllers\Controller; -use App\Notifications\SendMailChange; +use App\Mail\EmailChange; use App\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -33,7 +33,7 @@ class UMSController extends Controller public function index($page = 1) { $pages = User::count(); - $pages = (int) ceil($pages/25); + $pages = (int) ceil($pages/10); if($pages < 1 && $page == 1) $page = 1; @@ -42,7 +42,7 @@ class UMSController extends Controller 'page' => ($pages > 0 ? $pages : 1) ]); - $data = User::select('id', 'uuid', 'last_active', 'is_admin', 'is_root')->get(); + $data = User::select('id', 'uuid', 'last_active', 'is_admin', 'is_root', 'email_verified_at')->get(); return view('panel.ums.index')->with([ 'users' => $data, 'page' => $page, @@ -62,6 +62,7 @@ class UMSController extends Controller if(!$user->is_root && !$user->is_admin) return Redirect::route('panel.ums'); + $user = User::findOrFail($id); return view('panel.ums.edit')->with([ 'user' => $user ]); @@ -86,27 +87,50 @@ class UMSController extends Controller 'token' => Str::random(12) ]); - Mail::to($user)->send(new SendMailChange($change)); + Mail::to($request->get('email'))->send(new EmailChange($change)); + die('Send! Email: '.$request->get('email')); + } + + $validator = Validator::make($request->all(), [ + 'verified' => ['required', 'boolean'] + ]); + + if(!$validator->passes()) + return Redirect::back()->withErrors($validator); + + if($request->get('verified')) { + $user->email_verified_at = empty($user->email_verified_at) ? date('Y-m-d H:i:s') : $user->email_verified_at; + } else { + $user->email_verified_at = null; } if(Auth::user()->is_root) { $validator = Validator::make($request->all(), [ - 'is_admin' => ['required', 'boolean'], - 'is_root' => ['required', 'boolean'] + 'admin' => ['required', 'numeric', 'min:0', 'max:2'], ]); - if(!$validator->passes()) + if (!$validator->passes()) return Redirect::back()->withErrors($validator); - $user->is_admin = $request->get('is_admin'); - $user->is_root = $request->get('is_root'); - if($user->save()) { - session()->flash('success', 'Successfully edited user: '.$user->uuid); + $admin = $request->get('admin'); + if ($admin == 1) { + $user->is_admin = 1; + $user->is_root = 0; + } else if($admin == 2) { + $user->is_admin = 0; + $user->is_root = 1; } else { - session()->flash('error', 'Unable to edit user: '.$user->uuid); + $user->is_admin = 0; + $user->is_root = 0; } } + if($user->save()) { + session()->flash('success', 'Successfully edited user: '.$user->username()); + } else { + session()->flash('error', 'Unable to edit user: '.$user->username()); + } + return Redirect::route('panel.ums'); } diff --git a/app/Http/Controllers/Profile/ChangeController.php b/app/Http/Controllers/Profile/ChangeController.php index e5204f1..85ae87c 100644 --- a/app/Http/Controllers/Profile/ChangeController.php +++ b/app/Http/Controllers/Profile/ChangeController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Profile; use App\ChangeEmail; use App\Http\Controllers\Controller; +use App\Mail\EmailChange; use App\Notifications\SendMailChange; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Validator; @@ -84,7 +85,7 @@ class ChangeController extends Controller 'token' => Str::random(12) ]); - Mail::to($user)->send(new SendMailChange($change)); + Mail::to($user->email)->send(new EmailChange($change)); session()->flash('email_success', 'Successfully requested email change'); return Redirect::back(); } @@ -92,7 +93,7 @@ class ChangeController extends Controller public function verifyEmail($id, $token, $email) { if(Auth::id() != $id) { session()->flash('email_error', 'Incorrect email change request: '.$email); - return Redirect::route('panel.change'); + return Redirect::route('change'); } $user = Auth::user(); @@ -104,14 +105,14 @@ class ChangeController extends Controller if(empty($model)) { session()->flash('email_error', 'Unable to change email address to: '.$email); - return Redirect::route('panel.change'); + return Redirect::route('change'); } $user->email = $email; $user->save(); $model->delete(); session()->flash('email_success', 'Successfully changed email address to: '.$email); - return Redirect::route('panel.change'); + return Redirect::route('change'); } } diff --git a/app/Http/Controllers/Profile/SecurityController.php b/app/Http/Controllers/Profile/SecurityController.php index addf570..1a1d5b7 100644 --- a/app/Http/Controllers/Profile/SecurityController.php +++ b/app/Http/Controllers/Profile/SecurityController.php @@ -36,7 +36,7 @@ class SecurityController extends Controller $pages = Session::where('user_id', Auth::id())->count(); $pages = (int) ceil($pages/10); if($page > $pages) - return redirect()->route('profile.security', ['page' => $pages]); + return redirect()->route('security', ['page' => $pages]); $sessions = Session::where('user_id', Auth::id())->where('id', '!=', session()->getId())->skip(($page - 1)*10)->take(($page != 1 ? 10 : 9))->orderBy('last_activity', 'desc')->get(); $array = ['TFA' => $tfa, 'pages' => $pages, 'page' => $page, 'sessions' => $sessions, 'agent' => new Agent()]; @@ -67,10 +67,10 @@ class SecurityController extends Controller public function session($id) { if(session()->getId() === $id) - return redirect()->route('profile.security'); + return redirect()->route('security'); Session::where(['id' => $id, 'user_id' => Auth::id()])->forceDelete(); - return redirect()->route('profile.security'); + return redirect()->route('security'); } } diff --git a/app/Http/Controllers/RidecountController.php b/app/Http/Controllers/RidecountController.php index 5d9aa0a..d14ff93 100644 --- a/app/Http/Controllers/RidecountController.php +++ b/app/Http/Controllers/RidecountController.php @@ -30,6 +30,7 @@ class RidecountController extends Controller $top10 = DB::table(DB::raw('ridecount, (SELECT @row_number:=0) AS t'))->select('uuid', DB::raw('SUM(`count`) AS `count`'), DB::raw('(@row_number:=@row_number + 1) AS `num`')) ->where('attractionId', '=', $attraction_id) ->whereRaw('YEARWEEK(date, 1) = YEARWEEK(CURDATE(), 1)') + ->whereRaw('YEAR(date) = YEAR(CURDATE())') ->groupBy('uuid') ->orderByDesc('count') ->take(10)->get()->all(); diff --git a/app/Http/Controllers/ShowController.php b/app/Http/Controllers/ShowController.php index 6391a61..ed3dfdc 100644 --- a/app/Http/Controllers/ShowController.php +++ b/app/Http/Controllers/ShowController.php @@ -29,15 +29,7 @@ class ShowController extends Controller */ public function index() { - $shows = Show::join('show_dates', 'show_dates.show_id', '=', 'shows.id') - ->whereRaw('`show_dates`.`date` > CURDATE()') - ->select('shows.*')->get()->all(); - - $data = []; - foreach($shows as $show) - if(!empty($show->getShowDates(Auth::user()->uuid))) - array_push($data, $show); - + $shows = DB::select(DB::raw('SELECT t1.* FROM `shows` AS t1 RIGHT JOIN `show_dates` AS t2 ON t1.`id` = t2.`show_id` WHERE t2.`date` > CURDATE() GROUP BY t1.`id`')); return view('show')->with([ 'shows' => $shows ]); diff --git a/app/Http/Controllers/ToggleTwoFactorController.php b/app/Http/Controllers/ToggleTwoFactorController.php index d411ad9..4706cb0 100644 --- a/app/Http/Controllers/ToggleTwoFactorController.php +++ b/app/Http/Controllers/ToggleTwoFactorController.php @@ -1,8 +1,7 @@ flash('success', 'Successfully enabled 2FA'); - return redirect()->route('account'); + return redirect()->route('security'); } else { if(!$validator->passes()) return Redirect::back()->withErrors($validator); diff --git a/app/Http/Controllers/TwoFactorController.php b/app/Http/Controllers/TwoFactorController.php index 7795b0a..a916347 100644 --- a/app/Http/Controllers/TwoFactorController.php +++ b/app/Http/Controllers/TwoFactorController.php @@ -1,8 +1,7 @@ \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, '2fa' => \App\Http\Middleware\TwoFactorAuthentication::class, 'mobile' => \App\Http\Middleware\NoMobile::class, + 'admin' => \App\Http\Middleware\AdminOnly::class, ]; /** diff --git a/app/Http/Middleware/AdminOnly.php b/app/Http/Middleware/AdminOnly.php new file mode 100644 index 0000000..94530bd --- /dev/null +++ b/app/Http/Middleware/AdminOnly.php @@ -0,0 +1,25 @@ +is_admin || Auth::user()->is_root) + return $next($request); + + return Redirect::route('home'); + } +} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index e4cec9c..7ac9d5c 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -17,10 +17,10 @@ class RedirectIfAuthenticated */ public function handle($request, Closure $next, $guard = null) { - if (Auth::guard($guard)->check()) { - return redirect('/home'); - } + if (Auth::guard($guard)->check()) + return redirect()->intended('/'); return $next($request); } + } diff --git a/app/Mail/EmailChange.php b/app/Mail/EmailChange.php new file mode 100644 index 0000000..c303fca --- /dev/null +++ b/app/Mail/EmailChange.php @@ -0,0 +1,40 @@ +change = $change; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $user = User::findOrFail($this->change->user_id); + return $this->subject('Change Email')->markdown('emails.change')->with([ + 'user' => $user, + 'token' => $this->change->token, + 'email' => $this->change->email + ]); + } +} diff --git a/app/Notifications/SendMailChange.php b/app/Notifications/SendMailChange.php deleted file mode 100644 index c309ff5..0000000 --- a/app/Notifications/SendMailChange.php +++ /dev/null @@ -1,49 +0,0 @@ -change = $change; - } - - /** - * Build the message. - * - * @return MailMessage - */ - public function build() - { - $user = User::findOrFail($this->change->user_id); - - return (new MailMessage) - ->subject('Change Email') - ->line('Dear '.$user->firstname.',') - ->line('Press the button bellow if you wish to change your current email-address') - ->action('Change Email', url('/change/email/'.$user->id.'/'.$this->change->token.'/'.$this->change->email)) - ->line('Is this email not directed to you or do you not wish to change your email? Than you may ignore this.'); - } -} diff --git a/app/Rules/UUID.php b/app/Rules/UUID.php index 8863fa2..bf5588e 100644 --- a/app/Rules/UUID.php +++ b/app/Rules/UUID.php @@ -31,6 +31,9 @@ class UUID implements Rule if(empty($uuid)) return false; + if($uuid === $value) + return false; + $user = User::where('uuid', '=', $uuid)->first(); return empty($user); } @@ -42,6 +45,6 @@ class UUID implements Rule */ public function message() { - return 'Username: :attribute is already in use'; + return 'Username: :value is incorrect or already in use'; } } diff --git a/app/Show.php b/app/Show.php index d07d885..d3a481d 100644 --- a/app/Show.php +++ b/app/Show.php @@ -38,7 +38,7 @@ class Show extends Model ->groupBy('show_dates.date'); if($uuid !== null) - $data = $data->where('seats.uuid', '!=', $uuid); + $data = $data->whereRaw( "`show_dates`.`date` NOT IN (SELECT date FROM seats WHERE uuid='".$uuid."' AND show_id='".$this->id."')"); $data = $data->get()->all(); $dates = []; diff --git a/app/ShowDate.php b/app/ShowDate.php new file mode 100644 index 0000000..5527383 --- /dev/null +++ b/app/ShowDate.php @@ -0,0 +1,31 @@ +belongsTo('\App\Show'); + } + +} diff --git a/app/User.php b/app/User.php index 6452d4a..1a9308b 100644 --- a/app/User.php +++ b/app/User.php @@ -3,6 +3,7 @@ namespace App; use App\Cache\Cache; +use Carbon\Carbon; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -40,6 +41,12 @@ class User extends Authenticatable implements MustVerifyEmail 'email_verified_at' => 'datetime', ]; + public function fixedUUID() { + $uuid = substr_replace($this->uuid, '-', 8, 0); + $uuid = substr_replace($uuid, '-', 13, 0); + $uuid = substr_replace($uuid, '-', 18, 0); + return substr_replace($uuid, '-', 23, 0); + } private $username; public function username() { @@ -61,7 +68,7 @@ class User extends Authenticatable implements MustVerifyEmail 'shows.title', 'shows.description', 'shows.image', 'seats.*' ]) ->where('seats.uuid', '=', $this->uuid) - ->where('seats.date', '>', 'CURRENT_TIMESTAMP()') + ->where('seats.date', '>', Carbon::now()) ->get()->all(); $this->shows = $shows; return !empty($shows); @@ -76,7 +83,7 @@ class User extends Authenticatable implements MustVerifyEmail 'shows.title', 'shows.description', 'shows.image', 'seats.*' ]) ->where('seats.uuid', '=', $this->uuid) - ->where('seats.date', '>', 'CURRENT_TIMESTAMP()') + ->where('seats.date', '>', Carbon::now()) ->get()->all(); $this->shows = $shows; return $shows; diff --git a/composer.json b/composer.json index e1326a3..869fb58 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "laravel/tinker": "^1.0", "pragmarx/google2fa-laravel": "^1.2", "torann/geoip": "^1.0", - "ext-openssl": "*" + "ext-openssl": "*", + "ext-http": "*" }, "require-dev": { "facade/ignition": "^1.4", diff --git a/config/database.php b/config/database.php index 199382d..78184ff 100644 --- a/config/database.php +++ b/config/database.php @@ -61,6 +61,15 @@ return [ 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], + 'modes' => [ + //'ONLY_FULL_GROUP_BY', // Disable this to allow grouping by one column + 'STRICT_TRANS_TABLES', + 'NO_ZERO_IN_DATE', + 'NO_ZERO_DATE', + 'ERROR_FOR_DIVISION_BY_ZERO', + 'NO_AUTO_CREATE_USER', + 'NO_ENGINE_SUBSTITUTION' + ], ], 'pgsql' => [ diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 5f59fa3..450c3f3 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -20,6 +20,8 @@ class CreateUsersTable extends Migration $table->timestamp('email_verified_at')->nullable(); $table->string('google2fa_secret')->nullable(); $table->string('password'); + $table->boolean('is_admin')->default(0); + $table->boolean('is_root')->default(0); $table->rememberToken(); $table->timestamp('last_active')->nullable(); $table->timestamps(); diff --git a/database/migrations/2019_09_20_141811_create_seats_table.php b/database/migrations/2019_09_20_141811_create_seats_table.php index 9a7cad1..665f1a3 100644 --- a/database/migrations/2019_09_20_141811_create_seats_table.php +++ b/database/migrations/2019_09_20_141811_create_seats_table.php @@ -20,6 +20,8 @@ class CreateSeatsTable extends Migration $table->integer('seat'); $table->string('voucher'); $table->timestamp('date'); + $table->boolean('loaded')->default(0); + $table->boolean('paid')->default(0); $table->timestamps(); }); } diff --git a/public/assets/control.js b/public/assets/control.js new file mode 100644 index 0000000..22cb7f8 --- /dev/null +++ b/public/assets/control.js @@ -0,0 +1,212 @@ +function getPacket(channel, type, payload) { + let packet = { + channel: channel, + data: { + type: type, + payload: payload + } + } + + return JSON.stringify(packet) +} + +function getClickPacket(uuid, item_id) { + return getPacket( + 'SERVER_IN_OPERATOR_CLICK', + 'nl.iobyte.parkplus.api.packet.payloads.operator.ClientOperatorClickPayload', + { + uuid: uuid, + item_id: item_id + } + ) +} + +$(document).on('click', "div.panel[id^='button-'] > div.panel-body > img", function() { + const parent = $(this).parent().parent(); + const id = parent.attr("id").replace("button-", ""); + + if(!socket || hasConnected !== true) + return + + socket.emit( + 'data', + getClickPacket( + fixUUID(window.settings.uuid), + id + ) + ); +}); + +window.sendMessage = function(message) { + if(hasConnected) + socket.send(message); +} + +let hasConnected = false +let socket = null +$(() => { + socket = io('websocket.iobyte.nl', { + query: "type=client&id=" + window.settings.id + "&uuid=" + fixUUID(window.settings.uuid) + "&attraction=" + window.settings.attraction + "&token=" + window.settings.pin, + autoConnect: false, + withCredentials: false + }) + + socket.on('connect', () => { + setTimeout(() => { + if(hasConnected) + return + + socket.close() + }, 5 * 1000) + }) + + socket.on('disconnect', () => { + const control = $("span#control"); + control.removeClass(); + control.addClass("control-status"); + control.addClass("status-closed"); + control.html("Lost Connection"); + setTimeout(() => { + if(hasConnected) + return + + socket.close() + }, 5 * 1000) + }) + + socket.on('error', e => { + socket.close() + console.log(e) + }) + + socket.on('close', () => { + hasConnected = true + const control = $("span#control"); + control.removeClass(); + control.addClass("control-status"); + control.addClass("status-closed"); + control.html("Disconnected"); + }) + + socket.on('data', payload => { + try { + payload = JSON.parse(payload) + } catch (e) { + return + } + + if(!payload) + return + + if(!payload.hasOwnProperty('channel')) + return + + let channel = payload.channel; + if(channel === 'CLIENT_KICK') { + socket.close() + return + } + + let data; + if(payload.hasOwnProperty('data')) { + data = payload.data.payload; + } else { + data = payload.data + } + + handlePacket(channel, data) + }) +}) + +function fixUUID(i) { + return i.substr(0,8) + + "-" + + i.substr(8,4) + + "-" + + i.substr(12,4) + + "-" + + i.substr(16,4) + + "-" + + i.substr(20) +} + +function handlePacket(channel, payload) { + switch (channel) { + case 'CLIENT_NOTIFICATION': + handleNotification(payload.title, payload.message) + break + case 'CLIENT_OPERATOR_REQUEST_REPLY': + handleReply(payload) + break + case 'CLIENT_ATTRACTION_STATE': + handleStatus(payload.status) + break + case 'CLIENT_OPERATOR_STATE': + handleState(payload.item_id, payload.state) + break + } +} + +function handleNotification(title, message) { + const msg = $("div#message"); + msg.html('
'+title+'
'); + console.log({ + title: title, + message: message, + }) +} + +function handleReply(payload) { + if(payload.hasOwnProperty('requestReply') && payload.requestReply === 'ACCEPT') { + const control = $("span#control"); + control.removeClass(); + control.addClass("control-status"); + control.addClass("status-open"); + control.html("Connected"); + + loadPanel(payload.data) + return + } + + socket.close(); + handleNotification( + 'Unable to operate ride', + payload.message + ) +} + +function jsUcfirst(str) { + return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); +} + +function handleStatus(payload) { + const status = $("span#status"); + status.html(jsUcfirst(payload.name)); + status.style.backgroundColor = payload.hex +} + +function handleState(id, state) { + let img = new Image(); + img.onload = () => { + $("div#button-"+id+" div.panel-body img").attr("src", state.cover); + $("div#button-"+id+" div.panel-body #text").html(state.name); + } + img.src = state.cover; +} + +function loadPanel(panel) { + for(let i = 0; i < panel.items.length; i++) { + let item = panel.items[i]; + let state = item.state + + const image = state.cover; + const title = item.name; + const id = item.id; + const text = state.name; + + if(i === 0) + $("div#panel").append('\n'); + + $("div#panel").append("

"+title+"

"+text+"

\n"); + } +} diff --git a/public/assets/css/ionicons.min.css b/public/assets/css/ionicons.min.css deleted file mode 100644 index baba9e9..0000000 --- a/public/assets/css/ionicons.min.css +++ /dev/null @@ -1,11 +0,0 @@ -@charset "UTF-8";/*! - Ionicons, v2.0.0 - Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ - https://twitter.com/benjsperry https://twitter.com/ionicframework - MIT License: https://github.com/driftyco/ionicons - - Android-style icons originally built by Google’s - Material Design Icons: https://github.com/google/material-design-icons - used under CC BY http://creativecommons.org/licenses/by/4.0/ - Modified icons to fit ionicon’s grid from original. -*/@font-face{font-family:"Ionicons";src:url("../fonts/ionicons.eot?v=2.0.0");src:url("../fonts/ionicons.eot?v=2.0.0#iefix") format("embedded-opentype"),url("../fonts/ionicons.ttf?v=2.0.0") format("truetype"),url("../fonts/ionicons.woff?v=2.0.0") format("woff"),url("../fonts/ionicons.svg?v=2.0.0#Ionicons") format("svg");font-weight:normal;font-style:normal}.ion,.ionicons,.ion-alert:before,.ion-alert-circled:before,.ion-android-add:before,.ion-android-add-circle:before,.ion-android-alarm-clock:before,.ion-android-alert:before,.ion-android-apps:before,.ion-android-archive:before,.ion-android-arrow-back:before,.ion-android-arrow-down:before,.ion-android-arrow-dropdown:before,.ion-android-arrow-dropdown-circle:before,.ion-android-arrow-dropleft:before,.ion-android-arrow-dropleft-circle:before,.ion-android-arrow-dropright:before,.ion-android-arrow-dropright-circle:before,.ion-android-arrow-dropup:before,.ion-android-arrow-dropup-circle:before,.ion-android-arrow-forward:before,.ion-android-arrow-up:before,.ion-android-attach:before,.ion-android-bar:before,.ion-android-bicycle:before,.ion-android-boat:before,.ion-android-bookmark:before,.ion-android-bulb:before,.ion-android-bus:before,.ion-android-calendar:before,.ion-android-call:before,.ion-android-camera:before,.ion-android-cancel:before,.ion-android-car:before,.ion-android-cart:before,.ion-android-chat:before,.ion-android-checkbox:before,.ion-android-checkbox-blank:before,.ion-android-checkbox-outline:before,.ion-android-checkbox-outline-blank:before,.ion-android-checkmark-circle:before,.ion-android-clipboard:before,.ion-android-close:before,.ion-android-cloud:before,.ion-android-cloud-circle:before,.ion-android-cloud-done:before,.ion-android-cloud-outline:before,.ion-android-color-palette:before,.ion-android-compass:before,.ion-android-contact:before,.ion-android-contacts:before,.ion-android-contract:before,.ion-android-create:before,.ion-android-delete:before,.ion-android-desktop:before,.ion-android-document:before,.ion-android-done:before,.ion-android-done-all:before,.ion-android-download:before,.ion-android-drafts:before,.ion-android-exit:before,.ion-android-expand:before,.ion-android-favorite:before,.ion-android-favorite-outline:before,.ion-android-film:before,.ion-android-folder:before,.ion-android-folder-open:before,.ion-android-funnel:before,.ion-android-globe:before,.ion-android-hand:before,.ion-android-hangout:before,.ion-android-happy:before,.ion-android-home:before,.ion-android-image:before,.ion-android-laptop:before,.ion-android-list:before,.ion-android-locate:before,.ion-android-lock:before,.ion-android-mail:before,.ion-android-map:before,.ion-android-menu:before,.ion-android-microphone:before,.ion-android-microphone-off:before,.ion-android-more-horizontal:before,.ion-android-more-vertical:before,.ion-android-navigate:before,.ion-android-notifications:before,.ion-android-notifications-none:before,.ion-android-notifications-off:before,.ion-android-open:before,.ion-android-options:before,.ion-android-people:before,.ion-android-person:before,.ion-android-person-add:before,.ion-android-phone-landscape:before,.ion-android-phone-portrait:before,.ion-android-pin:before,.ion-android-plane:before,.ion-android-playstore:before,.ion-android-print:before,.ion-android-radio-button-off:before,.ion-android-radio-button-on:before,.ion-android-refresh:before,.ion-android-remove:before,.ion-android-remove-circle:before,.ion-android-restaurant:before,.ion-android-sad:before,.ion-android-search:before,.ion-android-send:before,.ion-android-settings:before,.ion-android-share:before,.ion-android-share-alt:before,.ion-android-star:before,.ion-android-star-half:before,.ion-android-star-outline:before,.ion-android-stopwatch:before,.ion-android-subway:before,.ion-android-sunny:before,.ion-android-sync:before,.ion-android-textsms:before,.ion-android-time:before,.ion-android-train:before,.ion-android-unlock:before,.ion-android-upload:before,.ion-android-volume-down:before,.ion-android-volume-mute:before,.ion-android-volume-off:before,.ion-android-volume-up:before,.ion-android-walk:before,.ion-android-warning:before,.ion-android-watch:before,.ion-android-wifi:before,.ion-aperture:before,.ion-archive:before,.ion-arrow-down-a:before,.ion-arrow-down-b:before,.ion-arrow-down-c:before,.ion-arrow-expand:before,.ion-arrow-graph-down-left:before,.ion-arrow-graph-down-right:before,.ion-arrow-graph-up-left:before,.ion-arrow-graph-up-right:before,.ion-arrow-left-a:before,.ion-arrow-left-b:before,.ion-arrow-left-c:before,.ion-arrow-move:before,.ion-arrow-resize:before,.ion-arrow-return-left:before,.ion-arrow-return-right:before,.ion-arrow-right-a:before,.ion-arrow-right-b:before,.ion-arrow-right-c:before,.ion-arrow-shrink:before,.ion-arrow-swap:before,.ion-arrow-up-a:before,.ion-arrow-up-b:before,.ion-arrow-up-c:before,.ion-asterisk:before,.ion-at:before,.ion-backspace:before,.ion-backspace-outline:before,.ion-bag:before,.ion-battery-charging:before,.ion-battery-empty:before,.ion-battery-full:before,.ion-battery-half:before,.ion-battery-low:before,.ion-beaker:before,.ion-beer:before,.ion-bluetooth:before,.ion-bonfire:before,.ion-bookmark:before,.ion-bowtie:before,.ion-briefcase:before,.ion-bug:before,.ion-calculator:before,.ion-calendar:before,.ion-camera:before,.ion-card:before,.ion-cash:before,.ion-chatbox:before,.ion-chatbox-working:before,.ion-chatboxes:before,.ion-chatbubble:before,.ion-chatbubble-working:before,.ion-chatbubbles:before,.ion-checkmark:before,.ion-checkmark-circled:before,.ion-checkmark-round:before,.ion-chevron-down:before,.ion-chevron-left:before,.ion-chevron-right:before,.ion-chevron-up:before,.ion-clipboard:before,.ion-clock:before,.ion-close:before,.ion-close-circled:before,.ion-close-round:before,.ion-closed-captioning:before,.ion-cloud:before,.ion-code:before,.ion-code-download:before,.ion-code-working:before,.ion-coffee:before,.ion-compass:before,.ion-compose:before,.ion-connection-bars:before,.ion-contrast:before,.ion-crop:before,.ion-cube:before,.ion-disc:before,.ion-document:before,.ion-document-text:before,.ion-drag:before,.ion-earth:before,.ion-easel:before,.ion-edit:before,.ion-egg:before,.ion-eject:before,.ion-email:before,.ion-email-unread:before,.ion-erlenmeyer-flask:before,.ion-erlenmeyer-flask-bubbles:before,.ion-eye:before,.ion-eye-disabled:before,.ion-female:before,.ion-filing:before,.ion-film-marker:before,.ion-fireball:before,.ion-flag:before,.ion-flame:before,.ion-flash:before,.ion-flash-off:before,.ion-folder:before,.ion-fork:before,.ion-fork-repo:before,.ion-forward:before,.ion-funnel:before,.ion-gear-a:before,.ion-gear-b:before,.ion-grid:before,.ion-hammer:before,.ion-happy:before,.ion-happy-outline:before,.ion-headphone:before,.ion-heart:before,.ion-heart-broken:before,.ion-help:before,.ion-help-buoy:before,.ion-help-circled:before,.ion-home:before,.ion-icecream:before,.ion-image:before,.ion-images:before,.ion-information:before,.ion-information-circled:before,.ion-ionic:before,.ion-ios-alarm:before,.ion-ios-alarm-outline:before,.ion-ios-albums:before,.ion-ios-albums-outline:before,.ion-ios-americanfootball:before,.ion-ios-americanfootball-outline:before,.ion-ios-analytics:before,.ion-ios-analytics-outline:before,.ion-ios-arrow-back:before,.ion-ios-arrow-down:before,.ion-ios-arrow-forward:before,.ion-ios-arrow-left:before,.ion-ios-arrow-right:before,.ion-ios-arrow-thin-down:before,.ion-ios-arrow-thin-left:before,.ion-ios-arrow-thin-right:before,.ion-ios-arrow-thin-up:before,.ion-ios-arrow-up:before,.ion-ios-at:before,.ion-ios-at-outline:before,.ion-ios-barcode:before,.ion-ios-barcode-outline:before,.ion-ios-baseball:before,.ion-ios-baseball-outline:before,.ion-ios-basketball:before,.ion-ios-basketball-outline:before,.ion-ios-bell:before,.ion-ios-bell-outline:before,.ion-ios-body:before,.ion-ios-body-outline:before,.ion-ios-bolt:before,.ion-ios-bolt-outline:before,.ion-ios-book:before,.ion-ios-book-outline:before,.ion-ios-bookmarks:before,.ion-ios-bookmarks-outline:before,.ion-ios-box:before,.ion-ios-box-outline:before,.ion-ios-briefcase:before,.ion-ios-briefcase-outline:before,.ion-ios-browsers:before,.ion-ios-browsers-outline:before,.ion-ios-calculator:before,.ion-ios-calculator-outline:before,.ion-ios-calendar:before,.ion-ios-calendar-outline:before,.ion-ios-camera:before,.ion-ios-camera-outline:before,.ion-ios-cart:before,.ion-ios-cart-outline:before,.ion-ios-chatboxes:before,.ion-ios-chatboxes-outline:before,.ion-ios-chatbubble:before,.ion-ios-chatbubble-outline:before,.ion-ios-checkmark:before,.ion-ios-checkmark-empty:before,.ion-ios-checkmark-outline:before,.ion-ios-circle-filled:before,.ion-ios-circle-outline:before,.ion-ios-clock:before,.ion-ios-clock-outline:before,.ion-ios-close:before,.ion-ios-close-empty:before,.ion-ios-close-outline:before,.ion-ios-cloud:before,.ion-ios-cloud-download:before,.ion-ios-cloud-download-outline:before,.ion-ios-cloud-outline:before,.ion-ios-cloud-upload:before,.ion-ios-cloud-upload-outline:before,.ion-ios-cloudy:before,.ion-ios-cloudy-night:before,.ion-ios-cloudy-night-outline:before,.ion-ios-cloudy-outline:before,.ion-ios-cog:before,.ion-ios-cog-outline:before,.ion-ios-color-filter:before,.ion-ios-color-filter-outline:before,.ion-ios-color-wand:before,.ion-ios-color-wand-outline:before,.ion-ios-compose:before,.ion-ios-compose-outline:before,.ion-ios-contact:before,.ion-ios-contact-outline:before,.ion-ios-copy:before,.ion-ios-copy-outline:before,.ion-ios-crop:before,.ion-ios-crop-strong:before,.ion-ios-download:before,.ion-ios-download-outline:before,.ion-ios-drag:before,.ion-ios-email:before,.ion-ios-email-outline:before,.ion-ios-eye:before,.ion-ios-eye-outline:before,.ion-ios-fastforward:before,.ion-ios-fastforward-outline:before,.ion-ios-filing:before,.ion-ios-filing-outline:before,.ion-ios-film:before,.ion-ios-film-outline:before,.ion-ios-flag:before,.ion-ios-flag-outline:before,.ion-ios-flame:before,.ion-ios-flame-outline:before,.ion-ios-flask:before,.ion-ios-flask-outline:before,.ion-ios-flower:before,.ion-ios-flower-outline:before,.ion-ios-folder:before,.ion-ios-folder-outline:before,.ion-ios-football:before,.ion-ios-football-outline:before,.ion-ios-game-controller-a:before,.ion-ios-game-controller-a-outline:before,.ion-ios-game-controller-b:before,.ion-ios-game-controller-b-outline:before,.ion-ios-gear:before,.ion-ios-gear-outline:before,.ion-ios-glasses:before,.ion-ios-glasses-outline:before,.ion-ios-grid-view:before,.ion-ios-grid-view-outline:before,.ion-ios-heart:before,.ion-ios-heart-outline:before,.ion-ios-help:before,.ion-ios-help-empty:before,.ion-ios-help-outline:before,.ion-ios-home:before,.ion-ios-home-outline:before,.ion-ios-infinite:before,.ion-ios-infinite-outline:before,.ion-ios-information:before,.ion-ios-information-empty:before,.ion-ios-information-outline:before,.ion-ios-ionic-outline:before,.ion-ios-keypad:before,.ion-ios-keypad-outline:before,.ion-ios-lightbulb:before,.ion-ios-lightbulb-outline:before,.ion-ios-list:before,.ion-ios-list-outline:before,.ion-ios-location:before,.ion-ios-location-outline:before,.ion-ios-locked:before,.ion-ios-locked-outline:before,.ion-ios-loop:before,.ion-ios-loop-strong:before,.ion-ios-medical:before,.ion-ios-medical-outline:before,.ion-ios-medkit:before,.ion-ios-medkit-outline:before,.ion-ios-mic:before,.ion-ios-mic-off:before,.ion-ios-mic-outline:before,.ion-ios-minus:before,.ion-ios-minus-empty:before,.ion-ios-minus-outline:before,.ion-ios-monitor:before,.ion-ios-monitor-outline:before,.ion-ios-moon:before,.ion-ios-moon-outline:before,.ion-ios-more:before,.ion-ios-more-outline:before,.ion-ios-musical-note:before,.ion-ios-musical-notes:before,.ion-ios-navigate:before,.ion-ios-navigate-outline:before,.ion-ios-nutrition:before,.ion-ios-nutrition-outline:before,.ion-ios-paper:before,.ion-ios-paper-outline:before,.ion-ios-paperplane:before,.ion-ios-paperplane-outline:before,.ion-ios-partlysunny:before,.ion-ios-partlysunny-outline:before,.ion-ios-pause:before,.ion-ios-pause-outline:before,.ion-ios-paw:before,.ion-ios-paw-outline:before,.ion-ios-people:before,.ion-ios-people-outline:before,.ion-ios-person:before,.ion-ios-person-outline:before,.ion-ios-personadd:before,.ion-ios-personadd-outline:before,.ion-ios-photos:before,.ion-ios-photos-outline:before,.ion-ios-pie:before,.ion-ios-pie-outline:before,.ion-ios-pint:before,.ion-ios-pint-outline:before,.ion-ios-play:before,.ion-ios-play-outline:before,.ion-ios-plus:before,.ion-ios-plus-empty:before,.ion-ios-plus-outline:before,.ion-ios-pricetag:before,.ion-ios-pricetag-outline:before,.ion-ios-pricetags:before,.ion-ios-pricetags-outline:before,.ion-ios-printer:before,.ion-ios-printer-outline:before,.ion-ios-pulse:before,.ion-ios-pulse-strong:before,.ion-ios-rainy:before,.ion-ios-rainy-outline:before,.ion-ios-recording:before,.ion-ios-recording-outline:before,.ion-ios-redo:before,.ion-ios-redo-outline:before,.ion-ios-refresh:before,.ion-ios-refresh-empty:before,.ion-ios-refresh-outline:before,.ion-ios-reload:before,.ion-ios-reverse-camera:before,.ion-ios-reverse-camera-outline:before,.ion-ios-rewind:before,.ion-ios-rewind-outline:before,.ion-ios-rose:before,.ion-ios-rose-outline:before,.ion-ios-search:before,.ion-ios-search-strong:before,.ion-ios-settings:before,.ion-ios-settings-strong:before,.ion-ios-shuffle:before,.ion-ios-shuffle-strong:before,.ion-ios-skipbackward:before,.ion-ios-skipbackward-outline:before,.ion-ios-skipforward:before,.ion-ios-skipforward-outline:before,.ion-ios-snowy:before,.ion-ios-speedometer:before,.ion-ios-speedometer-outline:before,.ion-ios-star:before,.ion-ios-star-half:before,.ion-ios-star-outline:before,.ion-ios-stopwatch:before,.ion-ios-stopwatch-outline:before,.ion-ios-sunny:before,.ion-ios-sunny-outline:before,.ion-ios-telephone:before,.ion-ios-telephone-outline:before,.ion-ios-tennisball:before,.ion-ios-tennisball-outline:before,.ion-ios-thunderstorm:before,.ion-ios-thunderstorm-outline:before,.ion-ios-time:before,.ion-ios-time-outline:before,.ion-ios-timer:before,.ion-ios-timer-outline:before,.ion-ios-toggle:before,.ion-ios-toggle-outline:before,.ion-ios-trash:before,.ion-ios-trash-outline:before,.ion-ios-undo:before,.ion-ios-undo-outline:before,.ion-ios-unlocked:before,.ion-ios-unlocked-outline:before,.ion-ios-upload:before,.ion-ios-upload-outline:before,.ion-ios-videocam:before,.ion-ios-videocam-outline:before,.ion-ios-volume-high:before,.ion-ios-volume-low:before,.ion-ios-wineglass:before,.ion-ios-wineglass-outline:before,.ion-ios-world:before,.ion-ios-world-outline:before,.ion-ipad:before,.ion-iphone:before,.ion-ipod:before,.ion-jet:before,.ion-key:before,.ion-knife:before,.ion-laptop:before,.ion-leaf:before,.ion-levels:before,.ion-lightbulb:before,.ion-link:before,.ion-load-a:before,.ion-load-b:before,.ion-load-c:before,.ion-load-d:before,.ion-location:before,.ion-lock-combination:before,.ion-locked:before,.ion-log-in:before,.ion-log-out:before,.ion-loop:before,.ion-magnet:before,.ion-male:before,.ion-man:before,.ion-map:before,.ion-medkit:before,.ion-merge:before,.ion-mic-a:before,.ion-mic-b:before,.ion-mic-c:before,.ion-minus:before,.ion-minus-circled:before,.ion-minus-round:before,.ion-model-s:before,.ion-monitor:before,.ion-more:before,.ion-mouse:before,.ion-music-note:before,.ion-navicon:before,.ion-navicon-round:before,.ion-navigate:before,.ion-network:before,.ion-no-smoking:before,.ion-nuclear:before,.ion-outlet:before,.ion-paintbrush:before,.ion-paintbucket:before,.ion-paper-airplane:before,.ion-paperclip:before,.ion-pause:before,.ion-person:before,.ion-person-add:before,.ion-person-stalker:before,.ion-pie-graph:before,.ion-pin:before,.ion-pinpoint:before,.ion-pizza:before,.ion-plane:before,.ion-planet:before,.ion-play:before,.ion-playstation:before,.ion-plus:before,.ion-plus-circled:before,.ion-plus-round:before,.ion-podium:before,.ion-pound:before,.ion-power:before,.ion-pricetag:before,.ion-pricetags:before,.ion-printer:before,.ion-pull-request:before,.ion-qr-scanner:before,.ion-quote:before,.ion-radio-waves:before,.ion-record:before,.ion-refresh:before,.ion-reply:before,.ion-reply-all:before,.ion-ribbon-a:before,.ion-ribbon-b:before,.ion-sad:before,.ion-sad-outline:before,.ion-scissors:before,.ion-search:before,.ion-settings:before,.ion-share:before,.ion-shuffle:before,.ion-skip-backward:before,.ion-skip-forward:before,.ion-social-android:before,.ion-social-android-outline:before,.ion-social-angular:before,.ion-social-angular-outline:before,.ion-social-apple:before,.ion-social-apple-outline:before,.ion-social-bitcoin:before,.ion-social-bitcoin-outline:before,.ion-social-buffer:before,.ion-social-buffer-outline:before,.ion-social-chrome:before,.ion-social-chrome-outline:before,.ion-social-codepen:before,.ion-social-codepen-outline:before,.ion-social-css3:before,.ion-social-css3-outline:before,.ion-social-designernews:before,.ion-social-designernews-outline:before,.ion-social-dribbble:before,.ion-social-dribbble-outline:before,.ion-social-dropbox:before,.ion-social-dropbox-outline:before,.ion-social-euro:before,.ion-social-euro-outline:before,.ion-social-facebook:before,.ion-social-facebook-outline:before,.ion-social-foursquare:before,.ion-social-foursquare-outline:before,.ion-social-freebsd-devil:before,.ion-social-github:before,.ion-social-github-outline:before,.ion-social-google:before,.ion-social-google-outline:before,.ion-social-googleplus:before,.ion-social-googleplus-outline:before,.ion-social-hackernews:before,.ion-social-hackernews-outline:before,.ion-social-html5:before,.ion-social-html5-outline:before,.ion-social-instagram:before,.ion-social-instagram-outline:before,.ion-social-javascript:before,.ion-social-javascript-outline:before,.ion-social-linkedin:before,.ion-social-linkedin-outline:before,.ion-social-markdown:before,.ion-social-nodejs:before,.ion-social-octocat:before,.ion-social-pinterest:before,.ion-social-pinterest-outline:before,.ion-social-python:before,.ion-social-reddit:before,.ion-social-reddit-outline:before,.ion-social-rss:before,.ion-social-rss-outline:before,.ion-social-sass:before,.ion-social-skype:before,.ion-social-skype-outline:before,.ion-social-snapchat:before,.ion-social-snapchat-outline:before,.ion-social-tumblr:before,.ion-social-tumblr-outline:before,.ion-social-tux:before,.ion-social-twitch:before,.ion-social-twitch-outline:before,.ion-social-twitter:before,.ion-social-twitter-outline:before,.ion-social-usd:before,.ion-social-usd-outline:before,.ion-social-vimeo:before,.ion-social-vimeo-outline:before,.ion-social-whatsapp:before,.ion-social-whatsapp-outline:before,.ion-social-windows:before,.ion-social-windows-outline:before,.ion-social-wordpress:before,.ion-social-wordpress-outline:before,.ion-social-yahoo:before,.ion-social-yahoo-outline:before,.ion-social-yen:before,.ion-social-yen-outline:before,.ion-social-youtube:before,.ion-social-youtube-outline:before,.ion-soup-can:before,.ion-soup-can-outline:before,.ion-speakerphone:before,.ion-speedometer:before,.ion-spoon:before,.ion-star:before,.ion-stats-bars:before,.ion-steam:before,.ion-stop:before,.ion-thermometer:before,.ion-thumbsdown:before,.ion-thumbsup:before,.ion-toggle:before,.ion-toggle-filled:before,.ion-transgender:before,.ion-trash-a:before,.ion-trash-b:before,.ion-trophy:before,.ion-tshirt:before,.ion-tshirt-outline:before,.ion-umbrella:before,.ion-university:before,.ion-unlocked:before,.ion-upload:before,.ion-usb:before,.ion-videocamera:before,.ion-volume-high:before,.ion-volume-low:before,.ion-volume-medium:before,.ion-volume-mute:before,.ion-wand:before,.ion-waterdrop:before,.ion-wifi:before,.ion-wineglass:before,.ion-woman:before,.ion-wrench:before,.ion-xbox:before{display:inline-block;font-family:"Ionicons";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;text-rendering:auto;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ion-alert:before{content:"\f101"}.ion-alert-circled:before{content:"\f100"}.ion-android-add:before{content:"\f2c7"}.ion-android-add-circle:before{content:"\f359"}.ion-android-alarm-clock:before{content:"\f35a"}.ion-android-alert:before{content:"\f35b"}.ion-android-apps:before{content:"\f35c"}.ion-android-archive:before{content:"\f2c9"}.ion-android-arrow-back:before{content:"\f2ca"}.ion-android-arrow-down:before{content:"\f35d"}.ion-android-arrow-dropdown:before{content:"\f35f"}.ion-android-arrow-dropdown-circle:before{content:"\f35e"}.ion-android-arrow-dropleft:before{content:"\f361"}.ion-android-arrow-dropleft-circle:before{content:"\f360"}.ion-android-arrow-dropright:before{content:"\f363"}.ion-android-arrow-dropright-circle:before{content:"\f362"}.ion-android-arrow-dropup:before{content:"\f365"}.ion-android-arrow-dropup-circle:before{content:"\f364"}.ion-android-arrow-forward:before{content:"\f30f"}.ion-android-arrow-up:before{content:"\f366"}.ion-android-attach:before{content:"\f367"}.ion-android-bar:before{content:"\f368"}.ion-android-bicycle:before{content:"\f369"}.ion-android-boat:before{content:"\f36a"}.ion-android-bookmark:before{content:"\f36b"}.ion-android-bulb:before{content:"\f36c"}.ion-android-bus:before{content:"\f36d"}.ion-android-calendar:before{content:"\f2d1"}.ion-android-call:before{content:"\f2d2"}.ion-android-camera:before{content:"\f2d3"}.ion-android-cancel:before{content:"\f36e"}.ion-android-car:before{content:"\f36f"}.ion-android-cart:before{content:"\f370"}.ion-android-chat:before{content:"\f2d4"}.ion-android-checkbox:before{content:"\f374"}.ion-android-checkbox-blank:before{content:"\f371"}.ion-android-checkbox-outline:before{content:"\f373"}.ion-android-checkbox-outline-blank:before{content:"\f372"}.ion-android-checkmark-circle:before{content:"\f375"}.ion-android-clipboard:before{content:"\f376"}.ion-android-close:before{content:"\f2d7"}.ion-android-cloud:before{content:"\f37a"}.ion-android-cloud-circle:before{content:"\f377"}.ion-android-cloud-done:before{content:"\f378"}.ion-android-cloud-outline:before{content:"\f379"}.ion-android-color-palette:before{content:"\f37b"}.ion-android-compass:before{content:"\f37c"}.ion-android-contact:before{content:"\f2d8"}.ion-android-contacts:before{content:"\f2d9"}.ion-android-contract:before{content:"\f37d"}.ion-android-create:before{content:"\f37e"}.ion-android-delete:before{content:"\f37f"}.ion-android-desktop:before{content:"\f380"}.ion-android-document:before{content:"\f381"}.ion-android-done:before{content:"\f383"}.ion-android-done-all:before{content:"\f382"}.ion-android-download:before{content:"\f2dd"}.ion-android-drafts:before{content:"\f384"}.ion-android-exit:before{content:"\f385"}.ion-android-expand:before{content:"\f386"}.ion-android-favorite:before{content:"\f388"}.ion-android-favorite-outline:before{content:"\f387"}.ion-android-film:before{content:"\f389"}.ion-android-folder:before{content:"\f2e0"}.ion-android-folder-open:before{content:"\f38a"}.ion-android-funnel:before{content:"\f38b"}.ion-android-globe:before{content:"\f38c"}.ion-android-hand:before{content:"\f2e3"}.ion-android-hangout:before{content:"\f38d"}.ion-android-happy:before{content:"\f38e"}.ion-android-home:before{content:"\f38f"}.ion-android-image:before{content:"\f2e4"}.ion-android-laptop:before{content:"\f390"}.ion-android-list:before{content:"\f391"}.ion-android-locate:before{content:"\f2e9"}.ion-android-lock:before{content:"\f392"}.ion-android-mail:before{content:"\f2eb"}.ion-android-map:before{content:"\f393"}.ion-android-menu:before{content:"\f394"}.ion-android-microphone:before{content:"\f2ec"}.ion-android-microphone-off:before{content:"\f395"}.ion-android-more-horizontal:before{content:"\f396"}.ion-android-more-vertical:before{content:"\f397"}.ion-android-navigate:before{content:"\f398"}.ion-android-notifications:before{content:"\f39b"}.ion-android-notifications-none:before{content:"\f399"}.ion-android-notifications-off:before{content:"\f39a"}.ion-android-open:before{content:"\f39c"}.ion-android-options:before{content:"\f39d"}.ion-android-people:before{content:"\f39e"}.ion-android-person:before{content:"\f3a0"}.ion-android-person-add:before{content:"\f39f"}.ion-android-phone-landscape:before{content:"\f3a1"}.ion-android-phone-portrait:before{content:"\f3a2"}.ion-android-pin:before{content:"\f3a3"}.ion-android-plane:before{content:"\f3a4"}.ion-android-playstore:before{content:"\f2f0"}.ion-android-print:before{content:"\f3a5"}.ion-android-radio-button-off:before{content:"\f3a6"}.ion-android-radio-button-on:before{content:"\f3a7"}.ion-android-refresh:before{content:"\f3a8"}.ion-android-remove:before{content:"\f2f4"}.ion-android-remove-circle:before{content:"\f3a9"}.ion-android-restaurant:before{content:"\f3aa"}.ion-android-sad:before{content:"\f3ab"}.ion-android-search:before{content:"\f2f5"}.ion-android-send:before{content:"\f2f6"}.ion-android-settings:before{content:"\f2f7"}.ion-android-share:before{content:"\f2f8"}.ion-android-share-alt:before{content:"\f3ac"}.ion-android-star:before{content:"\f2fc"}.ion-android-star-half:before{content:"\f3ad"}.ion-android-star-outline:before{content:"\f3ae"}.ion-android-stopwatch:before{content:"\f2fd"}.ion-android-subway:before{content:"\f3af"}.ion-android-sunny:before{content:"\f3b0"}.ion-android-sync:before{content:"\f3b1"}.ion-android-textsms:before{content:"\f3b2"}.ion-android-time:before{content:"\f3b3"}.ion-android-train:before{content:"\f3b4"}.ion-android-unlock:before{content:"\f3b5"}.ion-android-upload:before{content:"\f3b6"}.ion-android-volume-down:before{content:"\f3b7"}.ion-android-volume-mute:before{content:"\f3b8"}.ion-android-volume-off:before{content:"\f3b9"}.ion-android-volume-up:before{content:"\f3ba"}.ion-android-walk:before{content:"\f3bb"}.ion-android-warning:before{content:"\f3bc"}.ion-android-watch:before{content:"\f3bd"}.ion-android-wifi:before{content:"\f305"}.ion-aperture:before{content:"\f313"}.ion-archive:before{content:"\f102"}.ion-arrow-down-a:before{content:"\f103"}.ion-arrow-down-b:before{content:"\f104"}.ion-arrow-down-c:before{content:"\f105"}.ion-arrow-expand:before{content:"\f25e"}.ion-arrow-graph-down-left:before{content:"\f25f"}.ion-arrow-graph-down-right:before{content:"\f260"}.ion-arrow-graph-up-left:before{content:"\f261"}.ion-arrow-graph-up-right:before{content:"\f262"}.ion-arrow-left-a:before{content:"\f106"}.ion-arrow-left-b:before{content:"\f107"}.ion-arrow-left-c:before{content:"\f108"}.ion-arrow-move:before{content:"\f263"}.ion-arrow-resize:before{content:"\f264"}.ion-arrow-return-left:before{content:"\f265"}.ion-arrow-return-right:before{content:"\f266"}.ion-arrow-right-a:before{content:"\f109"}.ion-arrow-right-b:before{content:"\f10a"}.ion-arrow-right-c:before{content:"\f10b"}.ion-arrow-shrink:before{content:"\f267"}.ion-arrow-swap:before{content:"\f268"}.ion-arrow-up-a:before{content:"\f10c"}.ion-arrow-up-b:before{content:"\f10d"}.ion-arrow-up-c:before{content:"\f10e"}.ion-asterisk:before{content:"\f314"}.ion-at:before{content:"\f10f"}.ion-backspace:before{content:"\f3bf"}.ion-backspace-outline:before{content:"\f3be"}.ion-bag:before{content:"\f110"}.ion-battery-charging:before{content:"\f111"}.ion-battery-empty:before{content:"\f112"}.ion-battery-full:before{content:"\f113"}.ion-battery-half:before{content:"\f114"}.ion-battery-low:before{content:"\f115"}.ion-beaker:before{content:"\f269"}.ion-beer:before{content:"\f26a"}.ion-bluetooth:before{content:"\f116"}.ion-bonfire:before{content:"\f315"}.ion-bookmark:before{content:"\f26b"}.ion-bowtie:before{content:"\f3c0"}.ion-briefcase:before{content:"\f26c"}.ion-bug:before{content:"\f2be"}.ion-calculator:before{content:"\f26d"}.ion-calendar:before{content:"\f117"}.ion-camera:before{content:"\f118"}.ion-card:before{content:"\f119"}.ion-cash:before{content:"\f316"}.ion-chatbox:before{content:"\f11b"}.ion-chatbox-working:before{content:"\f11a"}.ion-chatboxes:before{content:"\f11c"}.ion-chatbubble:before{content:"\f11e"}.ion-chatbubble-working:before{content:"\f11d"}.ion-chatbubbles:before{content:"\f11f"}.ion-checkmark:before{content:"\f122"}.ion-checkmark-circled:before{content:"\f120"}.ion-checkmark-round:before{content:"\f121"}.ion-chevron-down:before{content:"\f123"}.ion-chevron-left:before{content:"\f124"}.ion-chevron-right:before{content:"\f125"}.ion-chevron-up:before{content:"\f126"}.ion-clipboard:before{content:"\f127"}.ion-clock:before{content:"\f26e"}.ion-close:before{content:"\f12a"}.ion-close-circled:before{content:"\f128"}.ion-close-round:before{content:"\f129"}.ion-closed-captioning:before{content:"\f317"}.ion-cloud:before{content:"\f12b"}.ion-code:before{content:"\f271"}.ion-code-download:before{content:"\f26f"}.ion-code-working:before{content:"\f270"}.ion-coffee:before{content:"\f272"}.ion-compass:before{content:"\f273"}.ion-compose:before{content:"\f12c"}.ion-connection-bars:before{content:"\f274"}.ion-contrast:before{content:"\f275"}.ion-crop:before{content:"\f3c1"}.ion-cube:before{content:"\f318"}.ion-disc:before{content:"\f12d"}.ion-document:before{content:"\f12f"}.ion-document-text:before{content:"\f12e"}.ion-drag:before{content:"\f130"}.ion-earth:before{content:"\f276"}.ion-easel:before{content:"\f3c2"}.ion-edit:before{content:"\f2bf"}.ion-egg:before{content:"\f277"}.ion-eject:before{content:"\f131"}.ion-email:before{content:"\f132"}.ion-email-unread:before{content:"\f3c3"}.ion-erlenmeyer-flask:before{content:"\f3c5"}.ion-erlenmeyer-flask-bubbles:before{content:"\f3c4"}.ion-eye:before{content:"\f133"}.ion-eye-disabled:before{content:"\f306"}.ion-female:before{content:"\f278"}.ion-filing:before{content:"\f134"}.ion-film-marker:before{content:"\f135"}.ion-fireball:before{content:"\f319"}.ion-flag:before{content:"\f279"}.ion-flame:before{content:"\f31a"}.ion-flash:before{content:"\f137"}.ion-flash-off:before{content:"\f136"}.ion-folder:before{content:"\f139"}.ion-fork:before{content:"\f27a"}.ion-fork-repo:before{content:"\f2c0"}.ion-forward:before{content:"\f13a"}.ion-funnel:before{content:"\f31b"}.ion-gear-a:before{content:"\f13d"}.ion-gear-b:before{content:"\f13e"}.ion-grid:before{content:"\f13f"}.ion-hammer:before{content:"\f27b"}.ion-happy:before{content:"\f31c"}.ion-happy-outline:before{content:"\f3c6"}.ion-headphone:before{content:"\f140"}.ion-heart:before{content:"\f141"}.ion-heart-broken:before{content:"\f31d"}.ion-help:before{content:"\f143"}.ion-help-buoy:before{content:"\f27c"}.ion-help-circled:before{content:"\f142"}.ion-home:before{content:"\f144"}.ion-icecream:before{content:"\f27d"}.ion-image:before{content:"\f147"}.ion-images:before{content:"\f148"}.ion-information:before{content:"\f14a"}.ion-information-circled:before{content:"\f149"}.ion-ionic:before{content:"\f14b"}.ion-ios-alarm:before{content:"\f3c8"}.ion-ios-alarm-outline:before{content:"\f3c7"}.ion-ios-albums:before{content:"\f3ca"}.ion-ios-albums-outline:before{content:"\f3c9"}.ion-ios-americanfootball:before{content:"\f3cc"}.ion-ios-americanfootball-outline:before{content:"\f3cb"}.ion-ios-analytics:before{content:"\f3ce"}.ion-ios-analytics-outline:before{content:"\f3cd"}.ion-ios-arrow-back:before{content:"\f3cf"}.ion-ios-arrow-down:before{content:"\f3d0"}.ion-ios-arrow-forward:before{content:"\f3d1"}.ion-ios-arrow-left:before{content:"\f3d2"}.ion-ios-arrow-right:before{content:"\f3d3"}.ion-ios-arrow-thin-down:before{content:"\f3d4"}.ion-ios-arrow-thin-left:before{content:"\f3d5"}.ion-ios-arrow-thin-right:before{content:"\f3d6"}.ion-ios-arrow-thin-up:before{content:"\f3d7"}.ion-ios-arrow-up:before{content:"\f3d8"}.ion-ios-at:before{content:"\f3da"}.ion-ios-at-outline:before{content:"\f3d9"}.ion-ios-barcode:before{content:"\f3dc"}.ion-ios-barcode-outline:before{content:"\f3db"}.ion-ios-baseball:before{content:"\f3de"}.ion-ios-baseball-outline:before{content:"\f3dd"}.ion-ios-basketball:before{content:"\f3e0"}.ion-ios-basketball-outline:before{content:"\f3df"}.ion-ios-bell:before{content:"\f3e2"}.ion-ios-bell-outline:before{content:"\f3e1"}.ion-ios-body:before{content:"\f3e4"}.ion-ios-body-outline:before{content:"\f3e3"}.ion-ios-bolt:before{content:"\f3e6"}.ion-ios-bolt-outline:before{content:"\f3e5"}.ion-ios-book:before{content:"\f3e8"}.ion-ios-book-outline:before{content:"\f3e7"}.ion-ios-bookmarks:before{content:"\f3ea"}.ion-ios-bookmarks-outline:before{content:"\f3e9"}.ion-ios-box:before{content:"\f3ec"}.ion-ios-box-outline:before{content:"\f3eb"}.ion-ios-briefcase:before{content:"\f3ee"}.ion-ios-briefcase-outline:before{content:"\f3ed"}.ion-ios-browsers:before{content:"\f3f0"}.ion-ios-browsers-outline:before{content:"\f3ef"}.ion-ios-calculator:before{content:"\f3f2"}.ion-ios-calculator-outline:before{content:"\f3f1"}.ion-ios-calendar:before{content:"\f3f4"}.ion-ios-calendar-outline:before{content:"\f3f3"}.ion-ios-camera:before{content:"\f3f6"}.ion-ios-camera-outline:before{content:"\f3f5"}.ion-ios-cart:before{content:"\f3f8"}.ion-ios-cart-outline:before{content:"\f3f7"}.ion-ios-chatboxes:before{content:"\f3fa"}.ion-ios-chatboxes-outline:before{content:"\f3f9"}.ion-ios-chatbubble:before{content:"\f3fc"}.ion-ios-chatbubble-outline:before{content:"\f3fb"}.ion-ios-checkmark:before{content:"\f3ff"}.ion-ios-checkmark-empty:before{content:"\f3fd"}.ion-ios-checkmark-outline:before{content:"\f3fe"}.ion-ios-circle-filled:before{content:"\f400"}.ion-ios-circle-outline:before{content:"\f401"}.ion-ios-clock:before{content:"\f403"}.ion-ios-clock-outline:before{content:"\f402"}.ion-ios-close:before{content:"\f406"}.ion-ios-close-empty:before{content:"\f404"}.ion-ios-close-outline:before{content:"\f405"}.ion-ios-cloud:before{content:"\f40c"}.ion-ios-cloud-download:before{content:"\f408"}.ion-ios-cloud-download-outline:before{content:"\f407"}.ion-ios-cloud-outline:before{content:"\f409"}.ion-ios-cloud-upload:before{content:"\f40b"}.ion-ios-cloud-upload-outline:before{content:"\f40a"}.ion-ios-cloudy:before{content:"\f410"}.ion-ios-cloudy-night:before{content:"\f40e"}.ion-ios-cloudy-night-outline:before{content:"\f40d"}.ion-ios-cloudy-outline:before{content:"\f40f"}.ion-ios-cog:before{content:"\f412"}.ion-ios-cog-outline:before{content:"\f411"}.ion-ios-color-filter:before{content:"\f414"}.ion-ios-color-filter-outline:before{content:"\f413"}.ion-ios-color-wand:before{content:"\f416"}.ion-ios-color-wand-outline:before{content:"\f415"}.ion-ios-compose:before{content:"\f418"}.ion-ios-compose-outline:before{content:"\f417"}.ion-ios-contact:before{content:"\f41a"}.ion-ios-contact-outline:before{content:"\f419"}.ion-ios-copy:before{content:"\f41c"}.ion-ios-copy-outline:before{content:"\f41b"}.ion-ios-crop:before{content:"\f41e"}.ion-ios-crop-strong:before{content:"\f41d"}.ion-ios-download:before{content:"\f420"}.ion-ios-download-outline:before{content:"\f41f"}.ion-ios-drag:before{content:"\f421"}.ion-ios-email:before{content:"\f423"}.ion-ios-email-outline:before{content:"\f422"}.ion-ios-eye:before{content:"\f425"}.ion-ios-eye-outline:before{content:"\f424"}.ion-ios-fastforward:before{content:"\f427"}.ion-ios-fastforward-outline:before{content:"\f426"}.ion-ios-filing:before{content:"\f429"}.ion-ios-filing-outline:before{content:"\f428"}.ion-ios-film:before{content:"\f42b"}.ion-ios-film-outline:before{content:"\f42a"}.ion-ios-flag:before{content:"\f42d"}.ion-ios-flag-outline:before{content:"\f42c"}.ion-ios-flame:before{content:"\f42f"}.ion-ios-flame-outline:before{content:"\f42e"}.ion-ios-flask:before{content:"\f431"}.ion-ios-flask-outline:before{content:"\f430"}.ion-ios-flower:before{content:"\f433"}.ion-ios-flower-outline:before{content:"\f432"}.ion-ios-folder:before{content:"\f435"}.ion-ios-folder-outline:before{content:"\f434"}.ion-ios-football:before{content:"\f437"}.ion-ios-football-outline:before{content:"\f436"}.ion-ios-game-controller-a:before{content:"\f439"}.ion-ios-game-controller-a-outline:before{content:"\f438"}.ion-ios-game-controller-b:before{content:"\f43b"}.ion-ios-game-controller-b-outline:before{content:"\f43a"}.ion-ios-gear:before{content:"\f43d"}.ion-ios-gear-outline:before{content:"\f43c"}.ion-ios-glasses:before{content:"\f43f"}.ion-ios-glasses-outline:before{content:"\f43e"}.ion-ios-grid-view:before{content:"\f441"}.ion-ios-grid-view-outline:before{content:"\f440"}.ion-ios-heart:before{content:"\f443"}.ion-ios-heart-outline:before{content:"\f442"}.ion-ios-help:before{content:"\f446"}.ion-ios-help-empty:before{content:"\f444"}.ion-ios-help-outline:before{content:"\f445"}.ion-ios-home:before{content:"\f448"}.ion-ios-home-outline:before{content:"\f447"}.ion-ios-infinite:before{content:"\f44a"}.ion-ios-infinite-outline:before{content:"\f449"}.ion-ios-information:before{content:"\f44d"}.ion-ios-information-empty:before{content:"\f44b"}.ion-ios-information-outline:before{content:"\f44c"}.ion-ios-ionic-outline:before{content:"\f44e"}.ion-ios-keypad:before{content:"\f450"}.ion-ios-keypad-outline:before{content:"\f44f"}.ion-ios-lightbulb:before{content:"\f452"}.ion-ios-lightbulb-outline:before{content:"\f451"}.ion-ios-list:before{content:"\f454"}.ion-ios-list-outline:before{content:"\f453"}.ion-ios-location:before{content:"\f456"}.ion-ios-location-outline:before{content:"\f455"}.ion-ios-locked:before{content:"\f458"}.ion-ios-locked-outline:before{content:"\f457"}.ion-ios-loop:before{content:"\f45a"}.ion-ios-loop-strong:before{content:"\f459"}.ion-ios-medical:before{content:"\f45c"}.ion-ios-medical-outline:before{content:"\f45b"}.ion-ios-medkit:before{content:"\f45e"}.ion-ios-medkit-outline:before{content:"\f45d"}.ion-ios-mic:before{content:"\f461"}.ion-ios-mic-off:before{content:"\f45f"}.ion-ios-mic-outline:before{content:"\f460"}.ion-ios-minus:before{content:"\f464"}.ion-ios-minus-empty:before{content:"\f462"}.ion-ios-minus-outline:before{content:"\f463"}.ion-ios-monitor:before{content:"\f466"}.ion-ios-monitor-outline:before{content:"\f465"}.ion-ios-moon:before{content:"\f468"}.ion-ios-moon-outline:before{content:"\f467"}.ion-ios-more:before{content:"\f46a"}.ion-ios-more-outline:before{content:"\f469"}.ion-ios-musical-note:before{content:"\f46b"}.ion-ios-musical-notes:before{content:"\f46c"}.ion-ios-navigate:before{content:"\f46e"}.ion-ios-navigate-outline:before{content:"\f46d"}.ion-ios-nutrition:before{content:"\f470"}.ion-ios-nutrition-outline:before{content:"\f46f"}.ion-ios-paper:before{content:"\f472"}.ion-ios-paper-outline:before{content:"\f471"}.ion-ios-paperplane:before{content:"\f474"}.ion-ios-paperplane-outline:before{content:"\f473"}.ion-ios-partlysunny:before{content:"\f476"}.ion-ios-partlysunny-outline:before{content:"\f475"}.ion-ios-pause:before{content:"\f478"}.ion-ios-pause-outline:before{content:"\f477"}.ion-ios-paw:before{content:"\f47a"}.ion-ios-paw-outline:before{content:"\f479"}.ion-ios-people:before{content:"\f47c"}.ion-ios-people-outline:before{content:"\f47b"}.ion-ios-person:before{content:"\f47e"}.ion-ios-person-outline:before{content:"\f47d"}.ion-ios-personadd:before{content:"\f480"}.ion-ios-personadd-outline:before{content:"\f47f"}.ion-ios-photos:before{content:"\f482"}.ion-ios-photos-outline:before{content:"\f481"}.ion-ios-pie:before{content:"\f484"}.ion-ios-pie-outline:before{content:"\f483"}.ion-ios-pint:before{content:"\f486"}.ion-ios-pint-outline:before{content:"\f485"}.ion-ios-play:before{content:"\f488"}.ion-ios-play-outline:before{content:"\f487"}.ion-ios-plus:before{content:"\f48b"}.ion-ios-plus-empty:before{content:"\f489"}.ion-ios-plus-outline:before{content:"\f48a"}.ion-ios-pricetag:before{content:"\f48d"}.ion-ios-pricetag-outline:before{content:"\f48c"}.ion-ios-pricetags:before{content:"\f48f"}.ion-ios-pricetags-outline:before{content:"\f48e"}.ion-ios-printer:before{content:"\f491"}.ion-ios-printer-outline:before{content:"\f490"}.ion-ios-pulse:before{content:"\f493"}.ion-ios-pulse-strong:before{content:"\f492"}.ion-ios-rainy:before{content:"\f495"}.ion-ios-rainy-outline:before{content:"\f494"}.ion-ios-recording:before{content:"\f497"}.ion-ios-recording-outline:before{content:"\f496"}.ion-ios-redo:before{content:"\f499"}.ion-ios-redo-outline:before{content:"\f498"}.ion-ios-refresh:before{content:"\f49c"}.ion-ios-refresh-empty:before{content:"\f49a"}.ion-ios-refresh-outline:before{content:"\f49b"}.ion-ios-reload:before{content:"\f49d"}.ion-ios-reverse-camera:before{content:"\f49f"}.ion-ios-reverse-camera-outline:before{content:"\f49e"}.ion-ios-rewind:before{content:"\f4a1"}.ion-ios-rewind-outline:before{content:"\f4a0"}.ion-ios-rose:before{content:"\f4a3"}.ion-ios-rose-outline:before{content:"\f4a2"}.ion-ios-search:before{content:"\f4a5"}.ion-ios-search-strong:before{content:"\f4a4"}.ion-ios-settings:before{content:"\f4a7"}.ion-ios-settings-strong:before{content:"\f4a6"}.ion-ios-shuffle:before{content:"\f4a9"}.ion-ios-shuffle-strong:before{content:"\f4a8"}.ion-ios-skipbackward:before{content:"\f4ab"}.ion-ios-skipbackward-outline:before{content:"\f4aa"}.ion-ios-skipforward:before{content:"\f4ad"}.ion-ios-skipforward-outline:before{content:"\f4ac"}.ion-ios-snowy:before{content:"\f4ae"}.ion-ios-speedometer:before{content:"\f4b0"}.ion-ios-speedometer-outline:before{content:"\f4af"}.ion-ios-star:before{content:"\f4b3"}.ion-ios-star-half:before{content:"\f4b1"}.ion-ios-star-outline:before{content:"\f4b2"}.ion-ios-stopwatch:before{content:"\f4b5"}.ion-ios-stopwatch-outline:before{content:"\f4b4"}.ion-ios-sunny:before{content:"\f4b7"}.ion-ios-sunny-outline:before{content:"\f4b6"}.ion-ios-telephone:before{content:"\f4b9"}.ion-ios-telephone-outline:before{content:"\f4b8"}.ion-ios-tennisball:before{content:"\f4bb"}.ion-ios-tennisball-outline:before{content:"\f4ba"}.ion-ios-thunderstorm:before{content:"\f4bd"}.ion-ios-thunderstorm-outline:before{content:"\f4bc"}.ion-ios-time:before{content:"\f4bf"}.ion-ios-time-outline:before{content:"\f4be"}.ion-ios-timer:before{content:"\f4c1"}.ion-ios-timer-outline:before{content:"\f4c0"}.ion-ios-toggle:before{content:"\f4c3"}.ion-ios-toggle-outline:before{content:"\f4c2"}.ion-ios-trash:before{content:"\f4c5"}.ion-ios-trash-outline:before{content:"\f4c4"}.ion-ios-undo:before{content:"\f4c7"}.ion-ios-undo-outline:before{content:"\f4c6"}.ion-ios-unlocked:before{content:"\f4c9"}.ion-ios-unlocked-outline:before{content:"\f4c8"}.ion-ios-upload:before{content:"\f4cb"}.ion-ios-upload-outline:before{content:"\f4ca"}.ion-ios-videocam:before{content:"\f4cd"}.ion-ios-videocam-outline:before{content:"\f4cc"}.ion-ios-volume-high:before{content:"\f4ce"}.ion-ios-volume-low:before{content:"\f4cf"}.ion-ios-wineglass:before{content:"\f4d1"}.ion-ios-wineglass-outline:before{content:"\f4d0"}.ion-ios-world:before{content:"\f4d3"}.ion-ios-world-outline:before{content:"\f4d2"}.ion-ipad:before{content:"\f1f9"}.ion-iphone:before{content:"\f1fa"}.ion-ipod:before{content:"\f1fb"}.ion-jet:before{content:"\f295"}.ion-key:before{content:"\f296"}.ion-knife:before{content:"\f297"}.ion-laptop:before{content:"\f1fc"}.ion-leaf:before{content:"\f1fd"}.ion-levels:before{content:"\f298"}.ion-lightbulb:before{content:"\f299"}.ion-link:before{content:"\f1fe"}.ion-load-a:before{content:"\f29a"}.ion-load-b:before{content:"\f29b"}.ion-load-c:before{content:"\f29c"}.ion-load-d:before{content:"\f29d"}.ion-location:before{content:"\f1ff"}.ion-lock-combination:before{content:"\f4d4"}.ion-locked:before{content:"\f200"}.ion-log-in:before{content:"\f29e"}.ion-log-out:before{content:"\f29f"}.ion-loop:before{content:"\f201"}.ion-magnet:before{content:"\f2a0"}.ion-male:before{content:"\f2a1"}.ion-man:before{content:"\f202"}.ion-map:before{content:"\f203"}.ion-medkit:before{content:"\f2a2"}.ion-merge:before{content:"\f33f"}.ion-mic-a:before{content:"\f204"}.ion-mic-b:before{content:"\f205"}.ion-mic-c:before{content:"\f206"}.ion-minus:before{content:"\f209"}.ion-minus-circled:before{content:"\f207"}.ion-minus-round:before{content:"\f208"}.ion-model-s:before{content:"\f2c1"}.ion-monitor:before{content:"\f20a"}.ion-more:before{content:"\f20b"}.ion-mouse:before{content:"\f340"}.ion-music-note:before{content:"\f20c"}.ion-navicon:before{content:"\f20e"}.ion-navicon-round:before{content:"\f20d"}.ion-navigate:before{content:"\f2a3"}.ion-network:before{content:"\f341"}.ion-no-smoking:before{content:"\f2c2"}.ion-nuclear:before{content:"\f2a4"}.ion-outlet:before{content:"\f342"}.ion-paintbrush:before{content:"\f4d5"}.ion-paintbucket:before{content:"\f4d6"}.ion-paper-airplane:before{content:"\f2c3"}.ion-paperclip:before{content:"\f20f"}.ion-pause:before{content:"\f210"}.ion-person:before{content:"\f213"}.ion-person-add:before{content:"\f211"}.ion-person-stalker:before{content:"\f212"}.ion-pie-graph:before{content:"\f2a5"}.ion-pin:before{content:"\f2a6"}.ion-pinpoint:before{content:"\f2a7"}.ion-pizza:before{content:"\f2a8"}.ion-plane:before{content:"\f214"}.ion-planet:before{content:"\f343"}.ion-play:before{content:"\f215"}.ion-playstation:before{content:"\f30a"}.ion-plus:before{content:"\f218"}.ion-plus-circled:before{content:"\f216"}.ion-plus-round:before{content:"\f217"}.ion-podium:before{content:"\f344"}.ion-pound:before{content:"\f219"}.ion-power:before{content:"\f2a9"}.ion-pricetag:before{content:"\f2aa"}.ion-pricetags:before{content:"\f2ab"}.ion-printer:before{content:"\f21a"}.ion-pull-request:before{content:"\f345"}.ion-qr-scanner:before{content:"\f346"}.ion-quote:before{content:"\f347"}.ion-radio-waves:before{content:"\f2ac"}.ion-record:before{content:"\f21b"}.ion-refresh:before{content:"\f21c"}.ion-reply:before{content:"\f21e"}.ion-reply-all:before{content:"\f21d"}.ion-ribbon-a:before{content:"\f348"}.ion-ribbon-b:before{content:"\f349"}.ion-sad:before{content:"\f34a"}.ion-sad-outline:before{content:"\f4d7"}.ion-scissors:before{content:"\f34b"}.ion-search:before{content:"\f21f"}.ion-settings:before{content:"\f2ad"}.ion-share:before{content:"\f220"}.ion-shuffle:before{content:"\f221"}.ion-skip-backward:before{content:"\f222"}.ion-skip-forward:before{content:"\f223"}.ion-social-android:before{content:"\f225"}.ion-social-android-outline:before{content:"\f224"}.ion-social-angular:before{content:"\f4d9"}.ion-social-angular-outline:before{content:"\f4d8"}.ion-social-apple:before{content:"\f227"}.ion-social-apple-outline:before{content:"\f226"}.ion-social-bitcoin:before{content:"\f2af"}.ion-social-bitcoin-outline:before{content:"\f2ae"}.ion-social-buffer:before{content:"\f229"}.ion-social-buffer-outline:before{content:"\f228"}.ion-social-chrome:before{content:"\f4db"}.ion-social-chrome-outline:before{content:"\f4da"}.ion-social-codepen:before{content:"\f4dd"}.ion-social-codepen-outline:before{content:"\f4dc"}.ion-social-css3:before{content:"\f4df"}.ion-social-css3-outline:before{content:"\f4de"}.ion-social-designernews:before{content:"\f22b"}.ion-social-designernews-outline:before{content:"\f22a"}.ion-social-dribbble:before{content:"\f22d"}.ion-social-dribbble-outline:before{content:"\f22c"}.ion-social-dropbox:before{content:"\f22f"}.ion-social-dropbox-outline:before{content:"\f22e"}.ion-social-euro:before{content:"\f4e1"}.ion-social-euro-outline:before{content:"\f4e0"}.ion-social-facebook:before{content:"\f231"}.ion-social-facebook-outline:before{content:"\f230"}.ion-social-foursquare:before{content:"\f34d"}.ion-social-foursquare-outline:before{content:"\f34c"}.ion-social-freebsd-devil:before{content:"\f2c4"}.ion-social-github:before{content:"\f233"}.ion-social-github-outline:before{content:"\f232"}.ion-social-google:before{content:"\f34f"}.ion-social-google-outline:before{content:"\f34e"}.ion-social-googleplus:before{content:"\f235"}.ion-social-googleplus-outline:before{content:"\f234"}.ion-social-hackernews:before{content:"\f237"}.ion-social-hackernews-outline:before{content:"\f236"}.ion-social-html5:before{content:"\f4e3"}.ion-social-html5-outline:before{content:"\f4e2"}.ion-social-instagram:before{content:"\f351"}.ion-social-instagram-outline:before{content:"\f350"}.ion-social-javascript:before{content:"\f4e5"}.ion-social-javascript-outline:before{content:"\f4e4"}.ion-social-linkedin:before{content:"\f239"}.ion-social-linkedin-outline:before{content:"\f238"}.ion-social-markdown:before{content:"\f4e6"}.ion-social-nodejs:before{content:"\f4e7"}.ion-social-octocat:before{content:"\f4e8"}.ion-social-pinterest:before{content:"\f2b1"}.ion-social-pinterest-outline:before{content:"\f2b0"}.ion-social-python:before{content:"\f4e9"}.ion-social-reddit:before{content:"\f23b"}.ion-social-reddit-outline:before{content:"\f23a"}.ion-social-rss:before{content:"\f23d"}.ion-social-rss-outline:before{content:"\f23c"}.ion-social-sass:before{content:"\f4ea"}.ion-social-skype:before{content:"\f23f"}.ion-social-skype-outline:before{content:"\f23e"}.ion-social-snapchat:before{content:"\f4ec"}.ion-social-snapchat-outline:before{content:"\f4eb"}.ion-social-tumblr:before{content:"\f241"}.ion-social-tumblr-outline:before{content:"\f240"}.ion-social-tux:before{content:"\f2c5"}.ion-social-twitch:before{content:"\f4ee"}.ion-social-twitch-outline:before{content:"\f4ed"}.ion-social-twitter:before{content:"\f243"}.ion-social-twitter-outline:before{content:"\f242"}.ion-social-usd:before{content:"\f353"}.ion-social-usd-outline:before{content:"\f352"}.ion-social-vimeo:before{content:"\f245"}.ion-social-vimeo-outline:before{content:"\f244"}.ion-social-whatsapp:before{content:"\f4f0"}.ion-social-whatsapp-outline:before{content:"\f4ef"}.ion-social-windows:before{content:"\f247"}.ion-social-windows-outline:before{content:"\f246"}.ion-social-wordpress:before{content:"\f249"}.ion-social-wordpress-outline:before{content:"\f248"}.ion-social-yahoo:before{content:"\f24b"}.ion-social-yahoo-outline:before{content:"\f24a"}.ion-social-yen:before{content:"\f4f2"}.ion-social-yen-outline:before{content:"\f4f1"}.ion-social-youtube:before{content:"\f24d"}.ion-social-youtube-outline:before{content:"\f24c"}.ion-soup-can:before{content:"\f4f4"}.ion-soup-can-outline:before{content:"\f4f3"}.ion-speakerphone:before{content:"\f2b2"}.ion-speedometer:before{content:"\f2b3"}.ion-spoon:before{content:"\f2b4"}.ion-star:before{content:"\f24e"}.ion-stats-bars:before{content:"\f2b5"}.ion-steam:before{content:"\f30b"}.ion-stop:before{content:"\f24f"}.ion-thermometer:before{content:"\f2b6"}.ion-thumbsdown:before{content:"\f250"}.ion-thumbsup:before{content:"\f251"}.ion-toggle:before{content:"\f355"}.ion-toggle-filled:before{content:"\f354"}.ion-transgender:before{content:"\f4f5"}.ion-trash-a:before{content:"\f252"}.ion-trash-b:before{content:"\f253"}.ion-trophy:before{content:"\f356"}.ion-tshirt:before{content:"\f4f7"}.ion-tshirt-outline:before{content:"\f4f6"}.ion-umbrella:before{content:"\f2b7"}.ion-university:before{content:"\f357"}.ion-unlocked:before{content:"\f254"}.ion-upload:before{content:"\f255"}.ion-usb:before{content:"\f2b8"}.ion-videocamera:before{content:"\f256"}.ion-volume-high:before{content:"\f257"}.ion-volume-low:before{content:"\f258"}.ion-volume-medium:before{content:"\f259"}.ion-volume-mute:before{content:"\f25a"}.ion-wand:before{content:"\f358"}.ion-waterdrop:before{content:"\f25b"}.ion-wifi:before{content:"\f25c"}.ion-wineglass:before{content:"\f2b9"}.ion-woman:before{content:"\f25d"}.ion-wrench:before{content:"\f2ba"}.ion-xbox:before{content:"\f30c"} diff --git a/public/assets/css/panel.min.css b/public/assets/css/panel.min.css index 693cbc9..b8569ce 100644 --- a/public/assets/css/panel.min.css +++ b/public/assets/css/panel.min.css @@ -1,8 +1,8 @@ -body,html{width:100%;min-height:100%;margin:0;padding:0;position:absolute;background-color:#2980b9;background: url("../img/background.jpg") no-repeat center center fixed;-webkit-background-size: cover;-moz-background-size: cover;-o-background-size: cover;background-size: cover;font-family:sans-serif}.page{width:360px;padding:8% 0 0;margin:auto}.page > div,.page > form{position:relative;z-index:1;background:#fff;max-width:360px;margin:0 auto 100px;padding:15px 45px 15px 45px;box-shadow:0 0 20px 0 rgba(0,0,0,.2),0 5px 5px 0 rgba(0,0,0,.24);border-radius:3px}.page > div img,.page form img{width:80%;display:block;margin:auto;margin-bottom:15px!important}.page form .input-group{margin-bottom:10px}.alert{color:#fff!important;margin-bottom:5px}.alert-danger{background-color:#e74c3c!important;border:none!important}.alert-success{background-color:#2ecc71!important;border:none!important} +body,html{width:100%;min-height:100%;margin:0;padding:0;position:absolute;background-color:#2980b9;background: url("https://cdn.discordapp.com/attachments/706149937905270904/707677922462400532/Gradiant_5.png") no-repeat center center fixed;-webkit-background-size: cover;-moz-background-size: cover;-o-background-size: cover;background-size: cover;font-family:sans-serif}.page{width:360px;padding:8% 0 0;margin:auto}.page > div,.page > form{position:relative;z-index:1;background:#171717;color:#fff;max-width:360px;margin:0 auto 100px;padding:15px 45px 15px 45px;box-shadow:0 0 20px 0 rgba(0,0,0,.2),0 5px 5px 0 rgba(0,0,0,.24);border-radius:3px}.page > div img,.page form img{width:80%;display:block;margin:auto;margin-bottom:15px!important}.page form .input-group{margin-bottom:10px}.alert{color:#fff!important;margin-bottom:5px}.alert-danger{background-color:#e74c3c!important;border:none!important}.alert-success{background-color:#2ecc71!important;border:none!important} .btn-custom { outline: 0; - background: #2ecc71; + background: #e31c26; width: 180px; border: 0; padding: 5px; @@ -12,18 +12,44 @@ body,html{width:100%;min-height:100%;margin:0;padding:0;position:absolute;backgr border-radius: 3px; } +.input-group-addon { + background-color: #e31c26 !important; + border-color: #333 !important; + color: #fff; +} + +.form-control, +.form-control[disabled] { + background-color: #404040; + color: #fff; + border-color: #262626 !important; +} + .btn-custom:hover,.btn-custom:active,.btn-custom:focus { - background: #27ae60; + background: #cc1922; color: #fff; } .footer { margin-bottom: 0; - background-color: #fff; + background-color: #171717; position: absolute; bottom: 0; text-align: center; padding: 10px; width: 100%; text-decoration: none; + color: #fff; +} + +.footer img { + filter: brightness(0) invert(1); +} + +a { + color: #888; +} + +a:hover, a:focus { + color: #ccc; } diff --git a/public/assets/css/panel_default.min.css b/public/assets/css/panel_default.min.css new file mode 100644 index 0000000..693cbc9 --- /dev/null +++ b/public/assets/css/panel_default.min.css @@ -0,0 +1,29 @@ +body,html{width:100%;min-height:100%;margin:0;padding:0;position:absolute;background-color:#2980b9;background: url("../img/background.jpg") no-repeat center center fixed;-webkit-background-size: cover;-moz-background-size: cover;-o-background-size: cover;background-size: cover;font-family:sans-serif}.page{width:360px;padding:8% 0 0;margin:auto}.page > div,.page > form{position:relative;z-index:1;background:#fff;max-width:360px;margin:0 auto 100px;padding:15px 45px 15px 45px;box-shadow:0 0 20px 0 rgba(0,0,0,.2),0 5px 5px 0 rgba(0,0,0,.24);border-radius:3px}.page > div img,.page form img{width:80%;display:block;margin:auto;margin-bottom:15px!important}.page form .input-group{margin-bottom:10px}.alert{color:#fff!important;margin-bottom:5px}.alert-danger{background-color:#e74c3c!important;border:none!important}.alert-success{background-color:#2ecc71!important;border:none!important} + +.btn-custom { + outline: 0; + background: #2ecc71; + width: 180px; + border: 0; + padding: 5px; + color: #FFFFFF; + font-size: 17px; + cursor: pointer; + border-radius: 3px; +} + +.btn-custom:hover,.btn-custom:active,.btn-custom:focus { + background: #27ae60; + color: #fff; +} + +.footer { + margin-bottom: 0; + background-color: #fff; + position: absolute; + bottom: 0; + text-align: center; + padding: 10px; + width: 100%; + text-decoration: none; +} diff --git a/public/assets/css/skin.css b/public/assets/css/skin.css index 4749afd..d711e53 100644 --- a/public/assets/css/skin.css +++ b/public/assets/css/skin.css @@ -12,8 +12,8 @@ filter: brightness(0) invert(1); } -.bg-aqua { - background-color: #239af6 !important; +.bg-green { + background-color: #cc1922 !important; } .small-box .inner { @@ -40,7 +40,7 @@ } .main-header .navbar { - background-color: #239af6; + background-color: #cc1922; } .main-header .navbar .nav > li > a { @@ -54,7 +54,7 @@ .main-header .navbar .nav .open > a:hover, .main-header .navbar .nav .open > a:focus, .main-header .navbar .nav > .active > a { - background: #0984e3; + background: #e31c26; color: #fff; } @@ -64,7 +64,7 @@ .main-header .navbar .sidebar-toggle:hover { color: #f6f6f6; - background: #0984e3; + background: #e31c26; } .main-header .navbar .sidebar-toggle { @@ -72,12 +72,12 @@ } .main-header .navbar .sidebar-toggle:hover { - background-color: #0984e3; + background-color: #e31c26; } @media (max-width: 767px) { .main-header .navbar .dropdown-menu li.divider { - background-color: #0984e3; + background-color: #e31c26; } .main-header .navbar .dropdown-menu li a { @@ -85,22 +85,22 @@ } .main-header .navbar .dropdown-menu li a:hover { - background: #0984e3; + background: #e31c26; } } .main-header .logo { - background-color: #0984e3; + background-color: #e31c26; color: #ffffff; border-bottom: 0 solid transparent; } .main-header .logo:hover { - background-color: #0984e3; + background-color: #e31c26; } .main-header li.user-header { - background-color: #239af6; + background-color: #cc1922; } .content-header { @@ -110,7 +110,7 @@ .wrapper, .main-sidebar, .left-side { - background-color: #222d32; + background-color: #333; } .user-panel > .info, @@ -119,8 +119,8 @@ } .sidebar-menu > li.header { - color: #4b646f; - background: #1a2226; + color: #fff; + background: #1a1a1a; } .sidebar-menu > li > a { @@ -131,20 +131,20 @@ .sidebar-menu > li.active > a, .sidebar-menu > li.menu-open > a { color: #ffffff; - background: #1e282c; + background: #262626; } .sidebar-menu > li.active > a { - border-left-color: #239af6; + border-left-color: #e31c26; } .sidebar-menu > li > .treeview-menu { margin: 0 1px; - background: #2c3b41; + background: #1a1919; } .sidebar a { - color: #b8c7ce; + color: #999; } .sidebar a:hover { @@ -152,11 +152,10 @@ } .sidebar-menu .treeview-menu > li > a { - color: #8aa4af; + color: #ccc; } .sidebar-menu .treeview-menu > li.active > a, - .sidebar-menu .treeview-menu > li > a:hover { color: #fff; } @@ -195,10 +194,7 @@ .sidebar-form .btn { color: #999; - border-top-left-radius: 0; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; - border-bottom-left-radius: 0; + border-radius: 0 2px 2px 0; } .form-control { @@ -214,16 +210,18 @@ p { } .box.box-primary { - border-top-color: #239af6 !important; + border-top-color: #cc1922 !important; } .btn.btn-primary { - background-color: #239af6 !important; + border-color: #e31c26 !important; + background-color: #cc1922 !important; color: #fff !important; } .btn.btn-primary:hover { - background-color: #0984e3 !important; + border-color: #cc1922 !important; + background-color: #e31c26 !important; color: #fff !important; } @@ -284,8 +282,8 @@ input.form-control { border-bottom-left-radius: 3px !important; } -.bg-blue-gradient { - background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0984e3), color-stop(1, #239af6)) !important; +.bg-green-gradient { + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #e31c26), color-stop(1, #cc1922)) !important; } i.no { @@ -295,7 +293,246 @@ i.no { } i.yes { - color: #2ecc71; + color: #cc1922; font-weight: 900; font-size: 25px; } + +.pagination li.active a { + background-color: #cc1922; + border-color: transparent; +} + +.panel-theme { + border-color: #e31c26; +} + +.panel-theme>.panel-heading { + color: #fff !important; + background-color: #cc1922 !important; + border-color: #e31c26 !important; +} + +.panel-theme>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #e31c26 !important; +} + +.panel-theme>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #e31c26 !important; +} + +.sceditor-container, textarea.editor { + resize: vertical !important; + width: 100%; + max-width: 100%; + min-height: 200px; + border-radius: 3px 3px 0 0; +} + +.content-wrapper { + background-color: #171717; +} + +.content-header { + color: #fff; +} + +.content-header>.breadcrumb>li:not(.active)>a { + color: #fff; +} + +.content-header>.breadcrumb>.active { + color: #ccc; +} + +.main-footer { + background-color: #282727; + color: #fff; + border: 0; +} + +.main-footer img { + filter: brightness(0) invert(1); +} + +.box { + border-top-color: #e31c26; + background-color: #282727; +} + +.box .box-header { + color: #fff; +} + +.sceditor-container, textarea.editor { + resize: vertical !important; + width: 100%; + max-width: 100%; + min-height: 200px; + border-color: #262626 !important; + color: #fff !important; + background-color: #404040 !important; +} + +.sceditor-toolbar{ + color: #fff !important; + background-color: #262626 !important; +} + +.sceditor-dropdown label { + color: #fff !important; +} + +.sceditor-dropdown { + color: #fff !important; + background-color: #333 !important; + border-color: #262626 !important; +} + +.sceditor-dropdown textarea, +.sceditor-dropdown input { + background-color: #404040 !important; + border-color: #262626 !important; + margin: 0 !important; +} + +.sceditor-dropdown div input[type="button"] { + background-color: #404040 !important; + color: #fff !important; + border-color: #202326; + width: calc(100% - 10px); +} + +.sceditor-dropdown div input[type="button"]:hover { + background-color: #202326 !important; + color: #fff !important; + border-color: #333 !important; +} + +.sceditor-dropdown a.sceditor-fontsize-option { + color: #fff !important; +} + +.sceditor-dropdown a.sceditor-fontsize-option:hover { + color: #fff !important; + background-color: #404040 !important; +} + +.sceditor-toolbar .sceditor-group { + color: #fff !important; + background-color: #ccc !important; + border: 0 !important; +} + +.sceditor-container iframe { + color: #fff !important; + background-color: #404040 !important; +} + +.sceditor-font-picker div { + overflow-y: scroll !important; + height: 120px !important; +} + +.sceditor-font-option, +.sceditor-more { + color: #fff !important; +} + +.sceditor-font-option:hover, +.sceditor-more:hover { + background-color: #404040 !important; +} + +.table-bordered { + border-color: #262626 !important; + color: #fff !important; +} + +.table-bordered > thead > tr > th { + border-color: #e31c26; + background-color: #e31c26; +} + +.table-striped > tbody > tr > td { + background-color: #262626; + border-color: #666 !important; +} + +.table-bordered > tbody >tr > td:first-child { + border-left-color: #262626 !important; +} + +.table-bordered > tbody >tr:last-child > td { + border-bottom-color: #262626 !important; +} + + +.table-bordered > tbody >tr > td:last-child { + border-right-color: #262626 !important; +} + +.table-striped>tbody>tr:nth-of-type(odd) > td { + background-color: #404040; +} + +.pagination > li:first-child > a, +.pagination > li:last-child > a { + background-color: #e31c26 !important; + border-color: #262626 !important; +} + +.pagination > .disabled > a { + color: #ccc !important; +} + +.pagination li a { + background-color: #404040; + color: #fff; + border-color: #202326 !important; +} + +.pagination li:hover a { + background-color: #666; + color: #fff; +} + +.pagination li.active a, +.pagination li.active a:hover{ + background-color: #666; + color: #fff; +} + +.input-group-addon { + background-color: #e31c26 !important; + border-color: #333 !important; + color: #fff; +} + +.form-control, +.form-control[disabled] { + background-color: #404040; + color: #fff; + border-color: #262626 !important; +} + +.form-group > label { + color: #fff; +} + +.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p { + color: #fff !important; +} + +.navbar-nav>.user-menu>.dropdown-menu { + border: 2px solid #e6e6e6; +} + +.navbar-nav>.user-menu>.dropdown-menu { + border: 0 !important; + top: 55px +} + +.navbar-nav>.user-menu>.dropdown-menu>.user-footer { + background-color: #282727; +} diff --git a/public/assets/css/skin_default.css b/public/assets/css/skin_default.css new file mode 100644 index 0000000..a73bf0a --- /dev/null +++ b/public/assets/css/skin_default.css @@ -0,0 +1,334 @@ +.user-image, .user-header img { + border: 0 !important; + border-radius: 0 !important; +} + +.navbar-nav>.user-menu>.dropdown-menu>li.user-header>p { + color: #fff !important; + font-weight: 600; +} + +.logo-lg img, .logo-mini img { + filter: brightness(0) invert(1); +} + +.bg-green { + background-color: #2ecc71 !important; +} + +.small-box .inner { + min-height: 102px; +} + +.small-box .icon { + color: #fff !important; + font-size: 65px; + margin-top: 10px; +} + +.label.label-status { + line-height: unset !important; + font-size: 15px !important; +} + +.main-footer span { + font-weight: 600; +} + +.main-footer span a img { + height: 20px !important; +} + +.main-header .navbar { + background-color: #2ecc71; +} + +.main-header .navbar .nav > li > a { + color: #ffffff; +} + +.main-header .navbar .nav > li > a:hover, +.main-header .navbar .nav > li > a:active, +.main-header .navbar .nav > li > a:focus, +.main-header .navbar .nav .open > a, +.main-header .navbar .nav .open > a:hover, +.main-header .navbar .nav .open > a:focus, +.main-header .navbar .nav > .active > a { + background: #27ae60; + color: #fff; +} + +.main-header .navbar .sidebar-toggle { + color: #fff; +} + +.main-header .navbar .sidebar-toggle:hover { + color: #f6f6f6; + background: #27ae60; +} + +.main-header .navbar .sidebar-toggle { + color: #fff; +} + +.main-header .navbar .sidebar-toggle:hover { + background-color: #27ae60; +} + +@media (max-width: 767px) { + .main-header .navbar .dropdown-menu li.divider { + background-color: #27ae60; + } + + .main-header .navbar .dropdown-menu li a { + color: #fff; + } + + .main-header .navbar .dropdown-menu li a:hover { + background: #27ae60; + } +} + +.main-header .logo { + background-color: #27ae60; + color: #ffffff; + border-bottom: 0 solid transparent; +} + +.main-header .logo:hover { + background-color: #27ae60; +} + +.main-header li.user-header { + background-color: #2ecc71; +} + +.content-header { + background: transparent; +} + +.wrapper, +.main-sidebar, +.left-side { + background-color: #222d32; +} + +.user-panel > .info, +.user-panel > .info > a { + color: #fff; +} + +.sidebar-menu > li.header { + color: #4b646f; + background: #1a2226; +} + +.sidebar-menu > li > a { + border-left: 3px solid transparent; +} + +.sidebar-menu > li:hover > a, +.sidebar-menu > li.active > a, +.sidebar-menu > li.menu-open > a { + color: #ffffff; + background: #1e282c; +} + +.sidebar-menu > li.active > a { + border-left-color: #2ecc71; +} + +.sidebar-menu > li > .treeview-menu { + margin: 0 1px; + background: #2c3b41; +} + +.sidebar a { + color: #b8c7ce; +} + +.sidebar a:hover { + text-decoration: none; +} + +.sidebar-menu .treeview-menu > li > a { + color: #8aa4af; +} + +.sidebar-menu .treeview-menu > li.active > a, + +.sidebar-menu .treeview-menu > li > a:hover { + color: #fff; +} + +.sidebar-form { + border-radius: 3px; + border: 1px solid #374850; + margin: 10px 10px; +} + +.sidebar-form input[type="text"], +.sidebar-form .btn { + box-shadow: none; + background-color: #374850; + border: 1px solid transparent; + height: 35px; +} + +.sidebar-form input[type="text"] { + color: #666; + border-top-left-radius: 2px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 2px; +} + +.sidebar-form input[type="text"]:focus, +.sidebar-form input[type="text"]:focus + .input-group-btn .btn { + background-color: #fff; + color: #666; +} + +.sidebar-form input[type="text"]:focus + .input-group-btn .btn { + border-left-color: #fff; +} + +.sidebar-form .btn { + color: #999; + border-top-left-radius: 0; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 0; +} + +.form-control { + margin-bottom: 5px; +} + +.input-group { + margin-bottom: 15px; +} + +p { + word-break: break-word; +} + +.box.box-primary { + border-top-color: #2ecc71 !important; +} + +.btn.btn-primary { + border-color: #27ae60 !important; + background-color: #2ecc71 !important; + color: #fff !important; +} + +.btn.btn-primary:hover { + border-color: #2ecc71 !important; + background-color: #27ae60 !important; + color: #fff !important; +} + +.logo-mini img { + display: block; + width: 40px; + margin-left: 5px; + margin-top: 10px; +} + +.logo-lg img { + display: block; + height: 30px; + margin: auto; + margin-top: 10px !important; +} + +.help-block { + font-size: 12px; + margin-top: -12px; + margin-bottom: -5px; +} + +div.pull-left.image { + margin-top: 5px; +} + +th.ip, td.ip { + width: 335px; +} + +.role, .project-status { + font-weight: 700; + padding: 5px; + border-radius: 3px; + color: #fff; + background-color: transparent; + display: inline-block; + width: fit-content; +} + +div.btn-group, div.btn-group .multiselect { + width: 100% !important; + margin-top: -2px; +} + +div.form-group { + margin-top: -10px; +} + +input.form-control { + border-top-right-radius: 3px !important; + border-bottom-right-radius: 3px !important; +} + +.input-group .input-group-addon { + border-top-left-radius: 3px !important; + border-bottom-left-radius: 3px !important; +} + +.bg-green-gradient { + background: -webkit-gradient(linear, left bottom, left top, color-stop(0, #27ae60), color-stop(1, #2ecc71)) !important; +} + +i.no { + color: #e74c3c; + font-weight: 900; + font-size: 25px; +} + +i.yes { + color: #2ecc71; + font-weight: 900; + font-size: 25px; +} + +.pagination li.active a { + background-color: #2ecc71; + border-color: transparent; +} + +.panel-theme { + border-color: #27ae60; +} + +.panel-theme>.panel-heading { + color: #fff !important; + background-color: #2ecc71 !important; + border-color: #27ae60 !important; +} + +.panel-theme>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #27ae60 !important; +} + +.panel-theme>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #27ae60 !important; +} + +.sceditor-container, textarea.editor { + resize: vertical !important; + width: 100%; + max-width: 100%; + min-height: 200px; + border-radius: 3px 3px 0 0; +} diff --git a/public/assets/css/style.css b/public/assets/css/style.css index c91fc5f..3aa2d71 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -4,7 +4,7 @@ html, body { margin: 0; padding: 0; position: absolute; - background: url("../img/background.jpg") no-repeat center center fixed; + background: url("https://cdn.discordapp.com/attachments/706149937905270904/707677922462400532/Gradiant_5.png") no-repeat center center fixed; -webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; @@ -47,18 +47,19 @@ html, body { .login-page form button { outline: 0; - background: #2ecc71; - width: 100%; + background: #e31c26; + width: 180px; border: 0; - padding: 15px; + padding: 5px; color: #FFFFFF; - font-size: 20px; + font-size: 17px; cursor: pointer; border-radius: 3px; } .login-page form button:hover,.form button:active,.form button:focus { - background: #27ae60; + background: #cc1922; + color: #fff; } .login-page form .message { @@ -80,15 +81,6 @@ html, body { display: none; } -iframe { - border: 0; - width: 100%; - height: 91%; - margin: 0; - margin-top: -20px !important; - padding: 0; -} - ::-webkit-scrollbar { width: 0; background: transparent !important; @@ -103,7 +95,8 @@ iframe { .footer { margin-bottom: 0; - background-color: #fff; + background-color: #282727; + color: #fff; position: absolute; bottom: 0; text-align: center; @@ -118,6 +111,7 @@ iframe { .footer span a img { height: 20px !important; + filter: brightness(0) invert(1); } @@ -136,25 +130,25 @@ iframe { } .panel-theme { - border-color: #27ae60; + border-color: #cc1922; } .panel-theme>.panel-heading { color: #fff !important; - background-color: #2ecc71 !important; - border-color: #27ae60 !important; + background-color: #e31c26 !important; + border-color: #cc1922 !important; } .panel-theme>.panel-heading+.panel-collapse>.panel-body { - border-top-color: #27ae60 !important; + border-top-color: #cc1922 !important; } .panel-theme>.panel-footer+.panel-collapse>.panel-body { - border-bottom-color: #27ae60 !important; + border-bottom-color: #cc1922 !important; } .navbar.navbar-theme { - background-color: #2ecc71; + background-color: #e31c26; border-color: transparent; } @@ -175,52 +169,52 @@ iframe { .navbar.navbar-theme .navbar-nav > li > a:hover, .navbar.navbar-theme .navbar-nav > li > a:focus { color: #fff; - background-color: #27ae60; + background-color: #cc1922; } .navbar.navbar-theme .navbar-nav > .active > a, .navbar.navbar-theme .navbar-nav > .active > a:hover, .navbar.navbar-theme .navbar-nav > .active > a:focus { color: #fff; - background-color: #27ae60; + background-color: #cc1922; } .navbar.navbar-theme .navbar-nav > .open > a, .navbar.navbar-theme .navbar-nav > .open > a:hover, .navbar.navbar-theme .navbar-nav > .open > a:focus { color: #fff; - background-color: #27ae60; + background-color: #cc1922; } .navbar.navbar-theme .navbar-toggle { border-color: #fff; - color: #2ecc71; + color: #e31c26; } .navbar.navbar-theme .navbar-toggle:hover, .navbar.navbar-theme .navbar-toggle:focus { border-color: #fff; - color: #27ae60; + color: #cc1922; } .navbar.navbar-theme .navbar-toggle .icon-bar { background-color: #fff; - color: #27ae60; + color: #cc1922; } .navbar.navbar-theme .navbar-link { color: #fff; - background-color: #27ae60; + background-color: #cc1922; } .navbar.navbar-theme .navbar-link:hover { color: #fff; - background-color: #27ae60; + background-color: #cc1922; } .navbar.navbar-theme .dropdown-menu { - background-color: #2ecc71; - border-color: #27ae60; + background-color: #e31c26; + border-color: #cc1922; margin: 0 !important; padding: 0 !important; } @@ -238,15 +232,15 @@ iframe { .navbar.navbar-theme .dropdown-menu a:focus, .navbar.navbar-theme .dropdown-menu a:hover { color: #fff; - background-color: #27ae60; + background-color: #cc1922; } .dropdown-menu .divider { - background-color: #27ae60 !important; + background-color: #cc1922 !important; } .dropdown-menu .active a { - background-color: #27ae60 !important; + background-color: #cc1922 !important; } .news .panel-body .container-fluid { @@ -337,14 +331,14 @@ iframe { color: #fff; padding: 8px 16px; border: 1px solid transparent; - border-color: transparent transparent #27ae60 transparent; + border-color: transparent transparent #cc1922 transparent; cursor: pointer; user-select: none; } .select-items { position: absolute; - background-color: #2ecc71; + background-color: #e31c26; top: 100%; left: 0; right: 0; @@ -356,12 +350,88 @@ iframe { } .select-items div:hover, .same-as-selected { - background-color: #27ae60; + background-color: #cc1922; +} + +.input-group-addon { + background-color: #e31c26 !important; + border-color: #333 !important; + color: #fff; +} + +.form-group > label { + color: #fff; +} + +.table-bordered { + border-color: #262626 !important; + color: #fff !important; +} + +.table-bordered > thead > tr > th { + border-color: #e31c26; + background-color: #e31c26; +} + +.table-striped > tbody > tr > td { + background-color: #262626; + border-color: #666 !important; +} + +.table-bordered > tbody >tr > td:first-child { + border-left-color: #262626 !important; +} + +.table-bordered > tbody >tr:last-child > td { + border-bottom-color: #262626 !important; +} + + +.table-bordered > tbody >tr > td:last-child { + border-right-color: #262626 !important; +} + +.table-striped>tbody>tr:nth-of-type(odd) > td { + background-color: #404040; +} + +.pagination > li:first-child > a, +.pagination > li:last-child > a { + background-color: #e31c26 !important; + border-color: #262626 !important; +} + +.pagination > .disabled > a { + color: #ccc !important; +} + +.pagination li a { + background-color: #404040; + color: #fff; + border-color: #202326 !important; +} + +.pagination li:hover a { + background-color: #666; + color: #fff; +} + +.pagination li.active a, +.pagination li.active a:hover{ + background-color: #666; + color: #fff; +} + +.form-control, +.form-control[disabled] { + background-color: #404040; + color: #fff; + border-color: #262626 !important; } .btn-custom { outline: 0; - background: #2ecc71; + background: #e31c26; width: 180px; border: 0; padding: 5px; @@ -371,8 +441,14 @@ iframe { border-radius: 3px; } +.panel { + background-color: #282727; + color: #fff; +} + .btn-custom:hover,.btn-custom:active,.btn-custom:focus { - background: #27ae60; + background: #cc1922; + color: #fff; } .controls { @@ -393,16 +469,32 @@ iframe { font-weight: 900; } -.status.closed { - color: #e74c3c; +.control-status { + padding: 5px 10px 5px 10px; + border-radius: 5px; + background-color: transparent; + color: #333; + font-weight: 900; } -.status.loading { - color: #f39c12; +.control-status.status-unknown { + background-color: #7f8c8d; + color: #fff; } -.status.open { - color: #2ecc71; +.control-status.status-closed { + background-color: #c0392b; + color: #fff; +} + +.control-status.status-open { + background-color: #2ecc71; + color: #fff; +} + +.control-status.status-inactive { + background-color: #c0392b; + color: #fff; } .actionfoto { @@ -432,7 +524,7 @@ code i { } .pagination li.active a { - background-color: #2ecc71; + background-color: #e31c26; border-color: transparent; } diff --git a/public/assets/css/style_default.css b/public/assets/css/style_default.css new file mode 100644 index 0000000..d240770 --- /dev/null +++ b/public/assets/css/style_default.css @@ -0,0 +1,462 @@ +html, body { + width: 100%; + min-height: 100%; + margin: 0; + padding: 0; + position: absolute; + background: url("../img/background.jpg") no-repeat center center fixed; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + font-family: sans-serif; +} + +.login-page { + width: 360px; + padding: 8% 0 0; + margin: auto; +} + +.login-page form { + position: relative; + z-index: 1; + background: #FFFFFF; + max-width: 360px; + margin: 0 auto 100px; + padding: 15px 45px 45px 45px; + box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24); + border-radius: 3px; +} + +.login-page form h1 { + text-align: center; +} + +.login-page form input { + outline: 0; + background: #f2f2f2; + width: 100%; + border: 0; + margin: 0 0 15px; + padding: 15px; + box-sizing: border-box; + font-size: 14px; + border-radius: 3px; +} + +.login-page form button { + outline: 0; + background: #2ecc71; + width: 100%; + border: 0; + padding: 15px; + color: #FFFFFF; + font-size: 20px; + cursor: pointer; + border-radius: 3px; +} + +.login-page form button:hover,.form button:active,.form button:focus { + background: #27ae60; +} + +.login-page form .message { + margin: 15px 0 0; + color: #7f8c8d; + font-size: 14px; +} + +.login-page form .message a { + color: #27ae60; + text-decoration: none; +} + +.login-page form .message a:hover { + color: #2ecc71; +} + +.login-page form .register-form { + display: none; +} + +::-webkit-scrollbar { + width: 0; + background: transparent !important; +} + +.wrapper { + padding: 15px; + width: 100% !important; + margin-top: 75px !important; + margin-bottom: 40px !important; +} + +.footer { + margin-bottom: 0; + background-color: #fff; + position: absolute; + bottom: 0; + text-align: center; + padding: 10px; + width: 100%; + text-decoration: none; +} + +.footer span { + font-weight: 600; +} + +.footer span a img { + height: 20px !important; +} + + +.alert { + color: #fff !important; +} + +.alert-success { + background-color: #2ecc71 !important; + border: none !important; +} + +.alert-danger { + background-color: #e74c3c !important; + border: none !important; +} + +.panel-theme { + border-color: #27ae60; +} + +.panel-theme>.panel-heading { + color: #fff !important; + background-color: #2ecc71 !important; + border-color: #27ae60 !important; +} + +.panel-theme>.panel-heading+.panel-collapse>.panel-body { + border-top-color: #27ae60 !important; +} + +.panel-theme>.panel-footer+.panel-collapse>.panel-body { + border-bottom-color: #27ae60 !important; +} + +.navbar.navbar-theme { + background-color: #2ecc71; + border-color: transparent; +} + +.navbar.navbar-theme .navbar-brand { + color: #fff; +} + +.navbar.navbar-theme .navbar-brand:hover, +.navbar.navbar-theme .navbar-brand:focus { + color: #fff; +} + +.navbar.navbar-theme .navbar-nav > li > a { + color: #fff; + background-color: transparent; +} + +.navbar.navbar-theme .navbar-nav > li > a:hover, +.navbar.navbar-theme .navbar-nav > li > a:focus { + color: #fff; + background-color: #27ae60; +} + +.navbar.navbar-theme .navbar-nav > .active > a, +.navbar.navbar-theme .navbar-nav > .active > a:hover, +.navbar.navbar-theme .navbar-nav > .active > a:focus { + color: #fff; + background-color: #27ae60; +} + +.navbar.navbar-theme .navbar-nav > .open > a, +.navbar.navbar-theme .navbar-nav > .open > a:hover, +.navbar.navbar-theme .navbar-nav > .open > a:focus { + color: #fff; + background-color: #27ae60; +} + +.navbar.navbar-theme .navbar-toggle { + border-color: #fff; + color: #2ecc71; +} + +.navbar.navbar-theme .navbar-toggle:hover, +.navbar.navbar-theme .navbar-toggle:focus { + border-color: #fff; + color: #27ae60; +} + +.navbar.navbar-theme .navbar-toggle .icon-bar { + background-color: #fff; + color: #27ae60; +} + +.navbar.navbar-theme .navbar-link { + color: #fff; + background-color: #27ae60; +} + +.navbar.navbar-theme .navbar-link:hover { + color: #fff; + background-color: #27ae60; +} + +.navbar.navbar-theme .dropdown-menu { + background-color: #2ecc71; + border-color: #27ae60; + margin: 0 !important; + padding: 0 !important; +} + +.navbar.navbar-theme .dropdown-menu li { + margin: 0 !important; + padding: 0 !important; +} + +.navbar.navbar-theme .dropdown-menu a { + color: #fff; + padding: 7px !important; +} + +.navbar.navbar-theme .dropdown-menu a:focus, +.navbar.navbar-theme .dropdown-menu a:hover { + color: #fff; + background-color: #27ae60; +} + +.dropdown-menu .divider { + background-color: #27ae60 !important; +} + +.dropdown-menu .active a { + background-color: #27ae60 !important; +} + +.news .panel-body .container-fluid { + word-wrap: break-word !important; + overflow: hidden !important; + margin: 0; + padding: 0; + width: 100% !important; + height: auto !important; +} + +.checkbox { + margin-top: -10px; +} + +.checkbox input[type="checkbox"] { + opacity: 0; +} + +.checkbox label { + position: relative; + display: inline-block; + padding-left: 22px; +} + +.checkbox label::before, +.checkbox label::after { + position: absolute; + content: ""; + display: inline-block; +} + +.checkbox label::before{ + height: 16px; + width: 16px; + border: 1px solid; + left: 0px; + top: 3px; +} + +.checkbox label::after { + height: 5px; + width: 9px; + border-left: 2px solid; + border-bottom: 2px solid; + transform: rotate(-45deg); + left: 4px; + top: 7px; +} + +.checkbox input[type="checkbox"] + label::after { + content: none; +} + +.checkbox input[type="checkbox"]:checked + label::after { + content: ""; +} + +.custom-select { + position: relative; +} + +.custom-select select { + display: none; +} + +.select-selected { + background-color: #2ecc71; +} + +.select-selected:after { + position: absolute; + content: ""; + top: 14px; + right: 10px; + width: 0; + height: 0; + border: 6px solid transparent; + border-color: #fff transparent transparent transparent; +} + +.select-selected.select-arrow-active:after { + border-color: transparent transparent #fff transparent; + top: 7px; +} + +.select-items div,.select-selected { + color: #fff; + padding: 8px 16px; + border: 1px solid transparent; + border-color: transparent transparent #27ae60 transparent; + cursor: pointer; + user-select: none; +} + +.select-items { + position: absolute; + background-color: #2ecc71; + top: 100%; + left: 0; + right: 0; + z-index: 99; +} + +.select-hide { + display: none; +} + +.select-items div:hover, .same-as-selected { + background-color: #27ae60; +} + +.btn-custom { + outline: 0; + background: #2ecc71; + width: 180px; + border: 0; + padding: 5px; + color: #FFFFFF; + font-size: 17px; + cursor: pointer; + border-radius: 3px; +} + +.btn-custom:hover,.btn-custom:active,.btn-custom:focus { + background: #27ae60; +} + +.controls { + padding: 0 !important; + margin: 0 !important; +} + +.controls img { + width: 70% !important; + height: auto !important; +} + +.controls.disabled { + filter: grayscale(100%) !important; +} + +.status, .attraction { + font-weight: 900; +} + +.control-status { + padding: 5px 10px 5px 10px; + border-radius: 5px; + background-color: transparent; + color: #333; + font-weight: 900; +} + +.control-status.status-unknown { + background-color: #7f8c8d; + color: #fff; +} + +.control-status.status-closed { + background-color: #c0392b; + color: #fff; +} + +.control-status.status-open { + background-color: #2ecc71; + color: #fff; +} + +.control-status.status-inactive { + background-color: #c0392b; + color: #fff; +} + +.actionfoto { + width: calc(100% - 4px) !important; + height: auto; + border: 2px solid #fff; + border-radius: 5px; +} + +code { + background-color: #eef1f6; + color: #596981; + border-radius: 3px; + padding: 3px; + line-height: 1.4; + font-size: 85%; + border: 1px solid rgba(0, 0, 0, .1); + display: block; + width: 335px; + text-align: right; + margin: 0; +} + +code i { + float: left; + margin: 3px; +} + +.pagination li.active a { + background-color: #2ecc71; + border-color: transparent; +} + +.status, .attraction { + font-weight: 900; + color: #fff; +} + +.status { + padding: 5px; + border-radius: 3px; + text-align: center; +} + +.actionfoto { + width: calc(100% - 4px) !important; + height: auto; + border: 2px solid #fff; + border-radius: 5px; +} diff --git a/public/assets/development/formats/bbcode.js b/public/assets/development/formats/bbcode.js new file mode 100644 index 0000000..fc89cd0 --- /dev/null +++ b/public/assets/development/formats/bbcode.js @@ -0,0 +1,2735 @@ +/** + * SCEditor BBCode Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2011-2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor BBCode Format + * @author Sam Clarke + */ +(function (sceditor) { + /*eslint max-depth: off*/ + 'use strict'; + + var escapeEntities = sceditor.escapeEntities; + var escapeUriScheme = sceditor.escapeUriScheme; + var dom = sceditor.dom; + var utils = sceditor.utils; + + var css = dom.css; + var attr = dom.attr; + var is = dom.is; + var extend = utils.extend; + var each = utils.each; + + var IE_VER = sceditor.ie; + + // In IE < 11 a BR at the end of a block level element + // causes a double line break. + var IE_BR_FIX = IE_VER && IE_VER < 11; + + var EMOTICON_DATA_ATTR = 'data-sceditor-emoticon'; + + var getEditorCommand = sceditor.command.get; + + var QuoteType = { + /** @lends BBCodeParser.QuoteType */ + /** + * Always quote the attribute value + * @type {Number} + */ + always: 1, + + /** + * Never quote the attributes value + * @type {Number} + */ + never: 2, + + /** + * Only quote the attributes value when it contains spaces to equals + * @type {Number} + */ + auto: 3 + }; + + var defaultCommandsOverrides = { + bold: { + txtExec: ['[b]', '[/b]'] + }, + italic: { + txtExec: ['[i]', '[/i]'] + }, + underline: { + txtExec: ['[u]', '[/u]'] + }, + strike: { + txtExec: ['[s]', '[/s]'] + }, + subscript: { + txtExec: ['[sub]', '[/sub]'] + }, + superscript: { + txtExec: ['[sup]', '[/sup]'] + }, + left: { + txtExec: ['[left]', '[/left]'] + }, + center: { + txtExec: ['[center]', '[/center]'] + }, + right: { + txtExec: ['[right]', '[/right]'] + }, + justify: { + txtExec: ['[justify]', '[/justify]'] + }, + font: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('font')._dropDown( + editor, + caller, + function (fontName) { + editor.insertText( + '[font=' + fontName + ']', + '[/font]' + ); + } + ); + } + }, + size: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('size')._dropDown( + editor, + caller, + function (fontSize) { + editor.insertText( + '[size=' + fontSize + ']', + '[/size]' + ); + } + ); + } + }, + color: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('color')._dropDown( + editor, + caller, + function (color) { + editor.insertText( + '[color=' + color + ']', + '[/color]' + ); + } + ); + } + }, + bulletlist: { + txtExec: function (caller, selected) { + var content = ''; + + each(selected.split(/\r?\n/), function () { + content += (content ? '\n' : '') + + '[li]' + this + '[/li]'; + }); + + this.insertText('[ul]\n' + content + '\n[/ul]'); + } + }, + orderedlist: { + txtExec: function (caller, selected) { + var content = ''; + + each(selected.split(/\r?\n/), function () { + content += (content ? '\n' : '') + + '[li]' + this + '[/li]'; + }); + + this.insertText('[ol]\n' + content + '\n[/ol]'); + } + }, + table: { + txtExec: ['[table][tr][td]', '[/td][/tr][/table]'] + }, + horizontalrule: { + txtExec: ['[hr]'] + }, + code: { + txtExec: ['[code]', '[/code]'] + }, + image: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('image')._dropDown( + editor, + caller, + selected, + function (url, width, height) { + var attrs = ''; + + if (width) { + attrs += ' width=' + width; + } + + if (height) { + attrs += ' height=' + height; + } + + editor.insertText( + '[img' + attrs + ']' + url + '[/img]' + ); + } + ); + } + }, + email: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('email')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '[email=' + url + ']' + + (text || selected || url) + + '[/email]' + ); + } + ); + } + }, + link: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('link')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '[url=' + url + ']' + + (text || selected || url) + + '[/url]' + ); + } + ); + } + }, + quote: { + txtExec: ['[quote]', '[/quote]'] + }, + youtube: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('youtube')._dropDown( + editor, + caller, + function (id) { + editor.insertText('[youtube]' + id + '[/youtube]'); + } + ); + } + }, + rtl: { + txtExec: ['[rtl]', '[/rtl]'] + }, + ltr: { + txtExec: ['[ltr]', '[/ltr]'] + } + }; + + var bbcodeHandlers = { + // START_COMMAND: Bold + b: { + tags: { + b: null, + strong: null + }, + styles: { + // 401 is for FF 3.5 + 'font-weight': ['bold', 'bolder', '401', '700', '800', '900'] + }, + format: '[b]{0}[/b]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Italic + i: { + tags: { + i: null, + em: null + }, + styles: { + 'font-style': ['italic', 'oblique'] + }, + format: '[i]{0}[/i]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Underline + u: { + tags: { + u: null + }, + styles: { + 'text-decoration': ['underline'] + }, + format: '[u]{0}[/u]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Strikethrough + s: { + tags: { + s: null, + strike: null + }, + styles: { + 'text-decoration': ['line-through'] + }, + format: '[s]{0}[/s]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Subscript + sub: { + tags: { + sub: null + }, + format: '[sub]{0}[/sub]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Superscript + sup: { + tags: { + sup: null + }, + format: '[sup]{0}[/sup]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Font + font: { + tags: { + font: { + face: null + } + }, + styles: { + 'font-family': null + }, + quoteType: QuoteType.never, + format: function (element, content) { + var font; + + if (!is(element, 'font') || !(font = attr(element, 'face'))) { + font = css(element, 'font-family'); + } + + return '[font=' + _stripQuotes(font) + ']' + + content + '[/font]'; + }, + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Size + size: { + tags: { + font: { + size: null + } + }, + styles: { + 'font-size': null + }, + format: function (element, content) { + var fontSize = attr(element, 'size'), + size = 2; + + if (!fontSize) { + fontSize = css(element, 'fontSize'); + } + + // Most browsers return px value but IE returns 1-7 + if (fontSize.indexOf('px') > -1) { + // convert size to an int + fontSize = fontSize.replace('px', '') - 0; + + if (fontSize < 12) { + size = 1; + } + if (fontSize > 15) { + size = 3; + } + if (fontSize > 17) { + size = 4; + } + if (fontSize > 23) { + size = 5; + } + if (fontSize > 31) { + size = 6; + } + if (fontSize > 47) { + size = 7; + } + } else { + size = fontSize; + } + + return '[size=' + size + ']' + content + '[/size]'; + }, + html: '{!0}' + }, + // END_COMMAND + + // START_COMMAND: Color + color: { + tags: { + font: { + color: null + } + }, + styles: { + color: null + }, + quoteType: QuoteType.never, + format: function (elm, content) { + var color; + + if (!is(elm, 'font') || !(color = attr(elm, 'color'))) { + color = elm.style.color || css(elm, 'color'); + } + + return '[color=' + _normaliseColour(color) + ']' + + content + '[/color]'; + }, + html: function (token, attrs, content) { + return '' + content + ''; + } + }, + // END_COMMAND + + // START_COMMAND: Lists + ul: { + tags: { + ul: null + }, + breakStart: true, + isInline: false, + skipLastLineBreak: true, + format: '[ul]{0}[/ul]', + html: '' + }, + list: { + breakStart: true, + isInline: false, + skipLastLineBreak: true, + html: '' + }, + ol: { + tags: { + ol: null + }, + breakStart: true, + isInline: false, + skipLastLineBreak: true, + format: '[ol]{0}[/ol]', + html: '
    {0}
' + }, + li: { + tags: { + li: null + }, + isInline: false, + closedBy: ['/ul', '/ol', '/list', '*', 'li'], + format: '[li]{0}[/li]', + html: '
  • {0}
  • ' + }, + '*': { + isInline: false, + closedBy: ['/ul', '/ol', '/list', '*', 'li'], + html: '
  • {0}
  • ' + }, + // END_COMMAND + + // START_COMMAND: Table + table: { + tags: { + table: null + }, + isInline: false, + isHtmlInline: true, + skipLastLineBreak: true, + format: '[table]{0}[/table]', + html: '{0}
    ' + }, + tr: { + tags: { + tr: null + }, + isInline: false, + skipLastLineBreak: true, + format: '[tr]{0}[/tr]', + html: '{0}' + }, + th: { + tags: { + th: null + }, + allowsEmpty: true, + isInline: false, + format: '[th]{0}[/th]', + html: '{0}' + }, + td: { + tags: { + td: null + }, + allowsEmpty: true, + isInline: false, + format: '[td]{0}[/td]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Emoticons + emoticon: { + allowsEmpty: true, + tags: { + img: { + src: null, + 'data-sceditor-emoticon': null + } + }, + format: function (element, content) { + return attr(element, EMOTICON_DATA_ATTR) + content; + }, + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Horizontal Rule + hr: { + tags: { + hr: null + }, + allowsEmpty: true, + isSelfClosing: true, + isInline: false, + format: '[hr]{0}', + html: '
    ' + }, + // END_COMMAND + + // START_COMMAND: Image + img: { + allowsEmpty: true, + tags: { + img: { + src: null + } + }, + allowedChildren: ['#'], + quoteType: QuoteType.never, + format: function (element, content) { + var width, height, + attribs = '', + style = function (name) { + return element.style ? element.style[name] : null; + }; + + // check if this is an emoticon image + if (attr(element, EMOTICON_DATA_ATTR)) { + return content; + } + + width = attr(element, 'width') || style('width'); + height = attr(element, 'height') || style('height'); + + // only add width and height if one is specified + if ((element.complete && (width || height)) || + (width && height)) { + + attribs = '=' + dom.width(element) + 'x' + + dom.height(element); + } + + return '[img' + attribs + ']' + attr(element, 'src') + '[/img]'; + }, + html: function (token, attrs, content) { + var undef, width, height, match, + attribs = ''; + + // handle [img width=340 height=240]url[/img] + width = attrs.width; + height = attrs.height; + + // handle [img=340x240]url[/img] + if (attrs.defaultattr) { + match = attrs.defaultattr.split(/x/i); + + width = match[0]; + height = (match.length === 2 ? match[1] : match[0]); + } + + if (width !== undef) { + attribs += ' width="' + escapeEntities(width, true) + '"'; + } + + if (height !== undef) { + attribs += ' height="' + escapeEntities(height, true) + '"'; + } + + return ''; + } + }, + // END_COMMAND + + // START_COMMAND: URL + url: { + allowsEmpty: true, + tags: { + a: { + href: null + } + }, + quoteType: QuoteType.never, + format: function (element, content) { + var url = attr(element, 'href'); + + // make sure this link is not an e-mail, + // if it is return e-mail BBCode + if (url.substr(0, 7) === 'mailto:') { + return '[email="' + url.substr(7) + '"]' + + content + '[/email]'; + } + + return '[url=' + url + ']' + content + '[/url]'; + }, + html: function (token, attrs, content) { + attrs.defaultattr = + escapeEntities(attrs.defaultattr, true) || content; + + return '' + + content + ''; + } + }, + // END_COMMAND + + // START_COMMAND: E-mail + email: { + quoteType: QuoteType.never, + html: function (token, attrs, content) { + return '' + content + ''; + } + }, + // END_COMMAND + + // START_COMMAND: Quote + quote: { + tags: { + blockquote: null + }, + isInline: false, + quoteType: QuoteType.never, + format: function (element, content) { + var authorAttr = 'data-author'; + var author = ''; + var cite; + var children = element.children; + + for (var i = 0; !cite && i < children.length; i++) { + if (is(children[i], 'cite')) { + cite = children[i]; + } + } + + if (cite || attr(element, authorAttr)) { + author = cite && cite.textContent || + attr(element, authorAttr); + + attr(element, authorAttr, author); + + if (cite) { + element.removeChild(cite); + } + + content = this.elementToBbcode(element); + author = '=' + author.replace(/(^\s+|\s+$)/g, ''); + + if (cite) { + element.insertBefore(cite, element.firstChild); + } + } + + return '[quote' + author + ']' + content + '[/quote]'; + }, + html: function (token, attrs, content) { + if (attrs.defaultattr) { + content = '' + escapeEntities(attrs.defaultattr) + + '' + content; + } + + return '
    ' + content + '
    '; + } + }, + // END_COMMAND + + // START_COMMAND: Code + code: { + tags: { + code: null + }, + isInline: false, + allowedChildren: ['#', '#newline'], + format: '[code]{0}[/code]', + html: '{0}' + }, + // END_COMMAND + + + // START_COMMAND: Left + left: { + styles: { + 'text-align': [ + 'left', + '-webkit-left', + '-moz-left', + '-khtml-left' + ] + }, + isInline: false, + format: '[left]{0}[/left]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Centre + center: { + styles: { + 'text-align': [ + 'center', + '-webkit-center', + '-moz-center', + '-khtml-center' + ] + }, + isInline: false, + format: '[center]{0}[/center]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Right + right: { + styles: { + 'text-align': [ + 'right', + '-webkit-right', + '-moz-right', + '-khtml-right' + ] + }, + isInline: false, + format: '[right]{0}[/right]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Justify + justify: { + styles: { + 'text-align': [ + 'justify', + '-webkit-justify', + '-moz-justify', + '-khtml-justify' + ] + }, + isInline: false, + format: '[justify]{0}[/justify]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: YouTube + youtube: { + allowsEmpty: true, + tags: { + iframe: { + 'data-youtube-id': null + } + }, + format: function (element, content) { + element = attr(element, 'data-youtube-id'); + + return element ? '[youtube]' + element + '[/youtube]' : content; + }, + html: '' + }, + // END_COMMAND + + + // START_COMMAND: Rtl + rtl: { + styles: { + direction: ['rtl'] + }, + isInline: false, + format: '[rtl]{0}[/rtl]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Ltr + ltr: { + styles: { + direction: ['ltr'] + }, + isInline: false, + format: '[ltr]{0}[/ltr]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // this is here so that commands above can be removed + // without having to remove the , after the last one. + // Needed for IE. + ignore: {} + }; + + /** + * Formats a string replacing {name} with the values of + * obj.name properties. + * + * If there is no property for the specified {name} then + * it will be left intact. + * + * @param {string} str + * @param {Object} obj + * @return {string} + * @since 2.0.0 + */ + function formatBBCodeString(str, obj) { + return str.replace(/\{([^}]+)\}/g, function (match, group) { + var undef, + escape = true; + + if (group.charAt(0) === '!') { + escape = false; + group = group.substring(1); + } + + if (group === '0') { + escape = false; + } + + if (obj[group] === undef) { + return match; + } + + return escape ? escapeEntities(obj[group], true) : obj[group]; + }); + } + + /** + * Removes the first and last divs from the HTML. + * + * This is needed for pasting + * @param {string} html + * @return {string} + * @private + */ + function removeFirstLastDiv(html) { + var node, next, removeDiv, + output = document.createElement('div'); + + removeDiv = function (node, isFirst) { + // Don't remove divs that have styling + if (dom.hasStyling(node)) { + return; + } + + if (IE_BR_FIX || (node.childNodes.length !== 1 || + !is(node.firstChild, 'br'))) { + while ((next = node.firstChild)) { + output.insertBefore(next, node); + } + } + + if (isFirst) { + var lastChild = output.lastChild; + + if (node !== lastChild && is(lastChild, 'div') && + node.nextSibling === lastChild) { + output.insertBefore(document.createElement('br'), node); + } + } + + output.removeChild(node); + }; + + css(output, 'display', 'none'); + output.innerHTML = html.replace(/<\/div>\n/g, ''); + + if ((node = output.firstChild) && is(node, 'div')) { + removeDiv(node, true); + } + + if ((node = output.lastChild) && is(node, 'div')) { + removeDiv(node); + } + + return output.innerHTML; + } + + function isFunction(fn) { + return typeof fn === 'function'; + } + + /** + * Removes any leading or trailing quotes ('") + * + * @return string + * @since v1.4.0 + */ + function _stripQuotes(str) { + return str ? + str.replace(/\\(.)/g, '$1').replace(/^(["'])(.*?)\1$/, '$2') : str; + } + + /** + * Formats a string replacing {0}, {1}, {2}, ect. with + * the params provided + * + * @param {string} str The string to format + * @param {...string} arg The strings to replace + * @return {string} + * @since v1.4.0 + */ + function _formatString(str) { + var undef; + var args = arguments; + + return str.replace(/\{(\d+)\}/g, function (_, matchNum) { + return args[matchNum - 0 + 1] !== undef ? + args[matchNum - 0 + 1] : + '{' + matchNum + '}'; + }); + } + + var TOKEN_OPEN = 'open'; + var TOKEN_CONTENT = 'content'; + var TOKEN_NEWLINE = 'newline'; + var TOKEN_CLOSE = 'close'; + + + /* + * @typedef {Object} TokenizeToken + * @property {string} type + * @property {string} name + * @property {string} val + * @property {Object.} attrs + * @property {array} children + * @property {TokenizeToken} closing + */ + + /** + * Tokenize token object + * + * @param {string} type The type of token this is, + * should be one of tokenType + * @param {string} name The name of this token + * @param {string} val The originally matched string + * @param {array} attrs Any attributes. Only set on + * TOKEN_TYPE_OPEN tokens + * @param {array} children Any children of this token + * @param {TokenizeToken} closing This tokens closing tag. + * Only set on TOKEN_TYPE_OPEN tokens + * @class {TokenizeToken} + * @name {TokenizeToken} + * @memberOf BBCodeParser.prototype + */ + // eslint-disable-next-line max-params + function TokenizeToken(type, name, val, attrs, children, closing) { + var base = this; + + base.type = type; + base.name = name; + base.val = val; + base.attrs = attrs || {}; + base.children = children || []; + base.closing = closing || null; + }; + + TokenizeToken.prototype = { + /** @lends BBCodeParser.prototype.TokenizeToken */ + /** + * Clones this token + * + * @return {TokenizeToken} + */ + clone: function () { + var base = this; + + return new TokenizeToken( + base.type, + base.name, + base.val, + extend({}, base.attrs), + [], + base.closing ? base.closing.clone() : null + ); + }, + /** + * Splits this token at the specified child + * + * @param {TokenizeToken} splitAt The child to split at + * @return {TokenizeToken} The right half of the split token or + * empty clone if invalid splitAt lcoation + */ + splitAt: function (splitAt) { + var offsetLength; + var base = this; + var clone = base.clone(); + var offset = base.children.indexOf(splitAt); + + if (offset > -1) { + // Work out how many items are on the right side of the split + // to pass to splice() + offsetLength = base.children.length - offset; + clone.children = base.children.splice(offset, offsetLength); + } + + return clone; + } + }; + + + /** + * SCEditor BBCode parser class + * + * @param {Object} options + * @class BBCodeParser + * @name BBCodeParser + * @since v1.4.0 + */ + function BBCodeParser(options) { + var base = this; + + base.opts = extend({}, BBCodeParser.defaults, options); + + /** + * Takes a BBCode string and splits it into open, + * content and close tags. + * + * It does no checking to verify a tag has a matching open + * or closing tag or if the tag is valid child of any tag + * before it. For that the tokens should be passed to the + * parse function. + * + * @param {string} str + * @return {array} + * @memberOf BBCodeParser.prototype + */ + base.tokenize = function (str) { + var matches, type, i; + var tokens = []; + // The token types in reverse order of precedence + // (they're looped in reverse) + var tokenTypes = [ + { + type: TOKEN_CONTENT, + regex: /^([^\[\r\n]+|\[)/ + }, + { + type: TOKEN_NEWLINE, + regex: /^(\r\n|\r|\n)/ + }, + { + type: TOKEN_OPEN, + regex: /^\[[^\[\]]+\]/ + }, + // Close must come before open as they are + // the same except close has a / at the start. + { + type: TOKEN_CLOSE, + regex: /^\[\/[^\[\]]+\]/ + } + ]; + + strloop: + while (str.length) { + i = tokenTypes.length; + while (i--) { + type = tokenTypes[i].type; + + // Check if the string matches any of the tokens + if (!(matches = str.match(tokenTypes[i].regex)) || + !matches[0]) { + continue; + } + + // Add the match to the tokens list + tokens.push(tokenizeTag(type, matches[0])); + + // Remove the match from the string + str = str.substr(matches[0].length); + + // The token has been added so start again + continue strloop; + } + + // If there is anything left in the string which doesn't match + // any of the tokens then just assume it's content and add it. + if (str.length) { + tokens.push(tokenizeTag(TOKEN_CONTENT, str)); + } + + str = ''; + } + + return tokens; + }; + + /** + * Extracts the name an params from a tag + * + * @param {string} type + * @param {string} val + * @return {Object} + * @private + */ + function tokenizeTag(type, val) { + var matches, attrs, name, + openRegex = /\[([^\]\s=]+)(?:([^\]]+))?\]/, + closeRegex = /\[\/([^\[\]]+)\]/; + + // Extract the name and attributes from opening tags and + // just the name from closing tags. + if (type === TOKEN_OPEN && (matches = val.match(openRegex))) { + name = lower(matches[1]); + + if (matches[2] && (matches[2] = matches[2].trim())) { + attrs = tokenizeAttrs(matches[2]); + } + } + + if (type === TOKEN_CLOSE && + (matches = val.match(closeRegex))) { + name = lower(matches[1]); + } + + if (type === TOKEN_NEWLINE) { + name = '#newline'; + } + + // Treat all tokens without a name and + // all unknown BBCodes as content + if (!name || ((type === TOKEN_OPEN || type === TOKEN_CLOSE) && + !bbcodeHandlers[name])) { + + type = TOKEN_CONTENT; + name = '#'; + } + + return new TokenizeToken(type, name, val, attrs); + } + + /** + * Extracts the individual attributes from a string containing + * all the attributes. + * + * @param {string} attrs + * @return {Object} Assoc array of attributes + * @private + */ + function tokenizeAttrs(attrs) { + var matches, + /* + ([^\s=]+) Anything that's not a space or equals + = Equals sign = + (?: + (?: + (["']) The opening quote + ( + (?:\\\2|[^\2])*? Anything that isn't the + unescaped opening quote + ) + \2 The opening quote again which + will close the string + ) + | If not a quoted string then match + ( + (?:.(?!\s\S+=))*.? Anything that isn't part of + [space][non-space][=] which + would be a new attribute + ) + ) + */ + attrRegex = /([^\s=]+)=(?:(?:(["'])((?:\\\2|[^\2])*?)\2)|((?:.(?!\s\S+=))*.))/g, + ret = {}; + + // if only one attribute then remove the = from the start and + // strip any quotes + if (attrs.charAt(0) === '=' && attrs.indexOf('=', 1) < 0) { + ret.defaultattr = _stripQuotes(attrs.substr(1)); + } else { + if (attrs.charAt(0) === '=') { + attrs = 'defaultattr' + attrs; + } + + // No need to strip quotes here, the regex will do that. + while ((matches = attrRegex.exec(attrs))) { + ret[lower(matches[1])] = + _stripQuotes(matches[3]) || matches[4]; + } + } + + return ret; + } + + /** + * Parses a string into an array of BBCodes + * + * @param {string} str + * @param {boolean} preserveNewLines If to preserve all new lines, not + * strip any based on the passed + * formatting options + * @return {array} Array of BBCode objects + * @memberOf BBCodeParser.prototype + */ + base.parse = function (str, preserveNewLines) { + var ret = parseTokens(base.tokenize(str)); + var opts = base.opts; + + if (opts.fixInvalidNesting) { + fixNesting(ret); + } + + normaliseNewLines(ret, null, preserveNewLines); + + if (opts.removeEmptyTags) { + removeEmpty(ret); + } + + return ret; + }; + + /** + * Checks if an array of TokenizeToken's contains the + * specified token. + * + * Checks the tokens name and type match another tokens + * name and type in the array. + * + * @param {string} name + * @param {string} type + * @param {array} arr + * @return {Boolean} + * @private + */ + function hasTag(name, type, arr) { + var i = arr.length; + + while (i--) { + if (arr[i].type === type && arr[i].name === name) { + return true; + } + } + + return false; + } + + /** + * Checks if the child tag is allowed as one + * of the parent tags children. + * + * @param {TokenizeToken} parent + * @param {TokenizeToken} child + * @return {Boolean} + * @private + */ + function isChildAllowed(parent, child) { + var parentBBCode = parent ? bbcodeHandlers[parent.name] : {}, + allowedChildren = parentBBCode.allowedChildren; + + if (base.opts.fixInvalidChildren && allowedChildren) { + return allowedChildren.indexOf(child.name || '#') > -1; + } + + return true; + } + + // TODO: Tidy this parseTokens() function up a bit. + /** + * Parses an array of tokens created by tokenize() + * + * @param {array} toks + * @return {array} Parsed tokens + * @see tokenize() + * @private + */ + function parseTokens(toks) { + var token, bbcode, curTok, clone, i, next, + cloned = [], + output = [], + openTags = [], + /** + * Returns the currently open tag or undefined + * @return {TokenizeToken} + */ + currentTag = function () { + return last(openTags); + }, + /** + * Adds a tag to either the current tags children + * or to the output array. + * @param {TokenizeToken} token + * @private + */ + addTag = function (token) { + if (currentTag()) { + currentTag().children.push(token); + } else { + output.push(token); + } + }, + /** + * Checks if this tag closes the current tag + * @param {string} name + * @return {Void} + */ + closesCurrentTag = function (name) { + return currentTag() && + (bbcode = bbcodeHandlers[currentTag().name]) && + bbcode.closedBy && + bbcode.closedBy.indexOf(name) > -1; + }; + + while ((token = toks.shift())) { + next = toks[0]; + + /* + * Fixes any invalid children. + * + * If it is an element which isn't allowed as a child of it's + * parent then it will be converted to content of the parent + * element. i.e. + * [code]Code [b]only[/b] allows text.[/code] + * Will become: + * Code [b]only[/b] allows text. + * Instead of: + * Code only allows text. + */ + // Ignore tags that can't be children + if (!isChildAllowed(currentTag(), token)) { + + // exclude closing tags of current tag + if (token.type !== TOKEN_CLOSE || !currentTag() || + token.name !== currentTag().name) { + token.name = '#'; + token.type = TOKEN_CONTENT; + } + } + + switch (token.type) { + case TOKEN_OPEN: + // Check it this closes a parent, + // e.g. for lists [*]one [*]two + if (closesCurrentTag(token.name)) { + openTags.pop(); + } + + addTag(token); + bbcode = bbcodeHandlers[token.name]; + + // If this tag is not self closing and it has a closing + // tag then it is open and has children so add it to the + // list of open tags. If has the closedBy property then + // it is closed by other tags so include everything as + // it's children until one of those tags is reached. + if (bbcode && !bbcode.isSelfClosing && + (bbcode.closedBy || + hasTag(token.name, TOKEN_CLOSE, toks))) { + openTags.push(token); + } else if (!bbcode || !bbcode.isSelfClosing) { + token.type = TOKEN_CONTENT; + } + break; + + case TOKEN_CLOSE: + // check if this closes the current tag, + // e.g. [/list] would close an open [*] + if (currentTag() && token.name !== currentTag().name && + closesCurrentTag('/' + token.name)) { + + openTags.pop(); + } + + // If this is closing the currently open tag just pop + // the close tag off the open tags array + if (currentTag() && token.name === currentTag().name) { + currentTag().closing = token; + openTags.pop(); + + // If this is closing an open tag that is the parent of + // the current tag then clone all the tags including the + // current one until reaching the parent that is being + // closed. Close the parent and then add the clones back + // in. + } else if (hasTag(token.name, TOKEN_OPEN, openTags)) { + + // Remove the tag from the open tags + while ((curTok = openTags.pop())) { + + // If it's the tag that is being closed then + // discard it and break the loop. + if (curTok.name === token.name) { + curTok.closing = token; + break; + } + + // Otherwise clone this tag and then add any + // previously cloned tags as it's children + clone = curTok.clone(); + + if (cloned.length) { + clone.children.push(last(cloned)); + } + + cloned.push(clone); + } + + // Place block linebreak before cloned tags + if (next && next.type === TOKEN_NEWLINE) { + bbcode = bbcodeHandlers[token.name]; + if (bbcode && bbcode.isInline === false) { + addTag(next); + toks.shift(); + } + } + + // Add the last cloned child to the now current tag + // (the parent of the tag which was being closed) + addTag(last(cloned)); + + // Add all the cloned tags to the open tags list + i = cloned.length; + while (i--) { + openTags.push(cloned[i]); + } + + cloned.length = 0; + + // This tag is closing nothing so treat it as content + } else { + token.type = TOKEN_CONTENT; + addTag(token); + } + break; + + case TOKEN_NEWLINE: + // handle things like + // [*]list\nitem\n[*]list1 + // where it should come out as + // [*]list\nitem[/*]\n[*]list1[/*] + // instead of + // [*]list\nitem\n[/*][*]list1[/*] + if (currentTag() && next && closesCurrentTag( + (next.type === TOKEN_CLOSE ? '/' : '') + + next.name + )) { + // skip if the next tag is the closing tag for + // the option tag, i.e. [/*] + if (!(next.type === TOKEN_CLOSE && + next.name === currentTag().name)) { + bbcode = bbcodeHandlers[currentTag().name]; + + if (bbcode && bbcode.breakAfter) { + openTags.pop(); + } else if (bbcode && + bbcode.isInline === false && + base.opts.breakAfterBlock && + bbcode.breakAfter !== false) { + openTags.pop(); + } + } + } + + addTag(token); + break; + + default: // content + addTag(token); + break; + } + } + + return output; + } + + /** + * Normalise all new lines + * + * Removes any formatting new lines from the BBCode + * leaving only content ones. I.e. for a list: + * + * [list] + * [*] list item one + * with a line break + * [*] list item two + * [/list] + * + * would become + * + * [list] [*] list item one + * with a line break [*] list item two [/list] + * + * Which makes it easier to convert to HTML or add + * the formatting new lines back in when converting + * back to BBCode + * + * @param {array} children + * @param {TokenizeToken} parent + * @param {boolean} onlyRemoveBreakAfter + * @return {void} + */ + function normaliseNewLines(children, parent, onlyRemoveBreakAfter) { + var token, left, right, parentBBCode, bbcode, + removedBreakEnd, removedBreakBefore, remove; + var childrenLength = children.length; + // TODO: this function really needs tidying up + if (parent) { + parentBBCode = bbcodeHandlers[parent.name]; + } + + var i = childrenLength; + while (i--) { + if (!(token = children[i])) { + continue; + } + + if (token.type === TOKEN_NEWLINE) { + left = i > 0 ? children[i - 1] : null; + right = i < childrenLength - 1 ? children[i + 1] : null; + remove = false; + + // Handle the start and end new lines + // e.g. [tag]\n and \n[/tag] + if (!onlyRemoveBreakAfter && parentBBCode && + parentBBCode.isSelfClosing !== true) { + // First child of parent so must be opening line break + // (breakStartBlock, breakStart) e.g. [tag]\n + if (!left) { + if (parentBBCode.isInline === false && + base.opts.breakStartBlock && + parentBBCode.breakStart !== false) { + remove = true; + } + + if (parentBBCode.breakStart) { + remove = true; + } + // Last child of parent so must be end line break + // (breakEndBlock, breakEnd) + // e.g. \n[/tag] + // remove last line break (breakEndBlock, breakEnd) + } else if (!removedBreakEnd && !right) { + if (parentBBCode.isInline === false && + base.opts.breakEndBlock && + parentBBCode.breakEnd !== false) { + remove = true; + } + + if (parentBBCode.breakEnd) { + remove = true; + } + + removedBreakEnd = remove; + } + } + + if (left && left.type === TOKEN_OPEN) { + if ((bbcode = bbcodeHandlers[left.name])) { + if (!onlyRemoveBreakAfter) { + if (bbcode.isInline === false && + base.opts.breakAfterBlock && + bbcode.breakAfter !== false) { + remove = true; + } + + if (bbcode.breakAfter) { + remove = true; + } + } else if (bbcode.isInline === false) { + remove = true; + } + } + } + + if (!onlyRemoveBreakAfter && !removedBreakBefore && + right && right.type === TOKEN_OPEN) { + + if ((bbcode = bbcodeHandlers[right.name])) { + if (bbcode.isInline === false && + base.opts.breakBeforeBlock && + bbcode.breakBefore !== false) { + remove = true; + } + + if (bbcode.breakBefore) { + remove = true; + } + + removedBreakBefore = remove; + + if (remove) { + children.splice(i, 1); + continue; + } + } + } + + if (remove) { + children.splice(i, 1); + } + + // reset double removedBreakBefore removal protection. + // This is needed for cases like \n\n[\tag] where + // only 1 \n should be removed but without this they both + // would be. + removedBreakBefore = false; + } else if (token.type === TOKEN_OPEN) { + normaliseNewLines(token.children, token, + onlyRemoveBreakAfter); + } + } + } + + /** + * Fixes any invalid nesting. + * + * If it is a block level element inside 1 or more inline elements + * then those inline elements will be split at the point where the + * block level is and the block level element placed between the split + * parts. i.e. + * [inline]A[blocklevel]B[/blocklevel]C[/inline] + * Will become: + * [inline]A[/inline][blocklevel]B[/blocklevel][inline]C[/inline] + * + * @param {array} children + * @param {array} [parents] Null if there is no parents + * @param {boolea} [insideInline] If inside an inline element + * @param {array} [rootArr] Root array if there is one + * @return {array} + * @private + */ + function fixNesting(children, parents, insideInline, rootArr) { + var token, i, parent, parentIndex, parentParentChildren, right; + + var isInline = function (token) { + var bbcode = bbcodeHandlers[token.name]; + + return !bbcode || bbcode.isInline !== false; + }; + + parents = parents || []; + rootArr = rootArr || children; + + // This must check the length each time as it can change when + // tokens are moved to fix the nesting. + for (i = 0; i < children.length; i++) { + if (!(token = children[i]) || token.type !== TOKEN_OPEN) { + continue; + } + + if (insideInline && !isInline(token)) { + // if this is a blocklevel element inside an inline one then + // split the parent at the block level element + parent = last(parents); + right = parent.splitAt(token); + + parentParentChildren = parents.length > 1 ? + parents[parents.length - 2].children : rootArr; + + // If parent inline is allowed inside this tag, clone it and + // wrap this tags children in it. + if (isChildAllowed(token, parent)) { + var clone = parent.clone(); + clone.children = token.children; + token.children = [clone]; + } + + parentIndex = parentParentChildren.indexOf(parent); + if (parentIndex > -1) { + // remove the block level token from the right side of + // the split inline element + right.children.splice(0, 1); + + // insert the block level token and the right side after + // the left side of the inline token + parentParentChildren.splice( + parentIndex + 1, 0, token, right + ); + + // If token is a block and is followed by a newline, + // then move the newline along with it to the new parent + var next = right.children[0]; + if (next && next.type === TOKEN_NEWLINE) { + if (!isInline(token)) { + right.children.splice(0, 1); + parentParentChildren.splice( + parentIndex + 2, 0, next + ); + } + } + + // return to parents loop as the + // children have now increased + return; + } + + } + + parents.push(token); + + fixNesting( + token.children, + parents, + insideInline || isInline(token), + rootArr + ); + + parents.pop(); + } + } + + /** + * Removes any empty BBCodes which are not allowed to be empty. + * + * @param {array} tokens + * @private + */ + function removeEmpty(tokens) { + var token, bbcode; + + /** + * Checks if all children are whitespace or not + * @private + */ + var isTokenWhiteSpace = function (children) { + var j = children.length; + + while (j--) { + var type = children[j].type; + + if (type === TOKEN_OPEN || type === TOKEN_CLOSE) { + return false; + } + + if (type === TOKEN_CONTENT && + /\S|\u00A0/.test(children[j].val)) { + return false; + } + } + + return true; + }; + + var i = tokens.length; + while (i--) { + // So skip anything that isn't a tag since only tags can be + // empty, content can't + if (!(token = tokens[i]) || token.type !== TOKEN_OPEN) { + continue; + } + + bbcode = bbcodeHandlers[token.name]; + + // Remove any empty children of this tag first so that if they + // are all removed this one doesn't think it's not empty. + removeEmpty(token.children); + + if (isTokenWhiteSpace(token.children) && bbcode && + !bbcode.isSelfClosing && !bbcode.allowsEmpty) { + tokens.splice.apply(tokens, [i, 1].concat(token.children)); + } + } + } + + /** + * Converts a BBCode string to HTML + * + * @param {string} str + * @param {boolean} preserveNewLines If to preserve all new lines, not + * strip any based on the passed + * formatting options + * @return {string} + * @memberOf BBCodeParser.prototype + */ + base.toHTML = function (str, preserveNewLines) { + return convertToHTML(base.parse(str, preserveNewLines), true); + }; + + /** + * @private + */ + function convertToHTML(tokens, isRoot) { + var undef, token, bbcode, content, html, needsBlockWrap, + blockWrapOpen, isInline, lastChild, + ret = []; + + isInline = function (bbcode) { + return (!bbcode || (bbcode.isHtmlInline !== undef ? + bbcode.isHtmlInline : bbcode.isInline)) !== false; + }; + + while (tokens.length > 0) { + if (!(token = tokens.shift())) { + continue; + } + + if (token.type === TOKEN_OPEN) { + lastChild = token.children[token.children.length - 1] || {}; + bbcode = bbcodeHandlers[token.name]; + needsBlockWrap = isRoot && isInline(bbcode); + content = convertToHTML(token.children, false); + + if (bbcode && bbcode.html) { + // Only add a line break to the end if this is + // blocklevel and the last child wasn't block-level + if (!isInline(bbcode) && + isInline(bbcodeHandlers[lastChild.name]) && + !bbcode.isPreFormatted && + !bbcode.skipLastLineBreak) { + // Add placeholder br to end of block level elements + // in all browsers apart from IE < 9 which handle + // new lines differently and doesn't need one. + if (!IE_BR_FIX) { + content += '
    '; + } + } + + if (!isFunction(bbcode.html)) { + token.attrs['0'] = content; + html = formatBBCodeString( + bbcode.html, + token.attrs + ); + } else { + html = bbcode.html.call( + base, + token, + token.attrs, + content + ); + } + } else { + html = token.val + content + + (token.closing ? token.closing.val : ''); + } + } else if (token.type === TOKEN_NEWLINE) { + if (!isRoot) { + ret.push('
    '); + continue; + } + + // If not already in a block wrap then start a new block + if (!blockWrapOpen) { + ret.push('
    '); + } + + // Putting BR in a div in IE causes it + // to do a double line break. + if (!IE_BR_FIX) { + ret.push('
    '); + } + + // Normally the div acts as a line-break with by moving + // whatever comes after onto a new line. + // If this is the last token, add an extra line-break so it + // shows as there will be nothing after it. + if (!tokens.length) { + ret.push('
    '); + } + + ret.push('
    \n'); + blockWrapOpen = false; + continue; + // content + } else { + needsBlockWrap = isRoot; + html = escapeEntities(token.val, true); + } + + if (needsBlockWrap && !blockWrapOpen) { + ret.push('
    '); + blockWrapOpen = true; + } else if (!needsBlockWrap && blockWrapOpen) { + ret.push('
    \n'); + blockWrapOpen = false; + } + + ret.push(html); + } + + if (blockWrapOpen) { + ret.push('\n'); + } + + return ret.join(''); + } + + /** + * Takes a BBCode string, parses it then converts it back to BBCode. + * + * This will auto fix the BBCode and format it with the specified + * options. + * + * @param {string} str + * @param {boolean} preserveNewLines If to preserve all new lines, not + * strip any based on the passed + * formatting options + * @return {string} + * @memberOf BBCodeParser.prototype + */ + base.toBBCode = function (str, preserveNewLines) { + return convertToBBCode(base.parse(str, preserveNewLines)); + }; + + /** + * Converts parsed tokens back into BBCode with the + * formatting specified in the options and with any + * fixes specified. + * + * @param {array} toks Array of parsed tokens from base.parse() + * @return {string} + * @private + */ + function convertToBBCode(toks) { + var token, attr, bbcode, isBlock, isSelfClosing, quoteType, + breakBefore, breakStart, breakEnd, breakAfter, + // Create an array of strings which are joined together + // before being returned as this is faster in slow browsers. + // (Old versions of IE). + ret = []; + + while (toks.length > 0) { + if (!(token = toks.shift())) { + continue; + } + // TODO: tidy this + bbcode = bbcodeHandlers[token.name]; + isBlock = !(!bbcode || bbcode.isInline !== false); + isSelfClosing = bbcode && bbcode.isSelfClosing; + + breakBefore = (isBlock && base.opts.breakBeforeBlock && + bbcode.breakBefore !== false) || + (bbcode && bbcode.breakBefore); + + breakStart = (isBlock && !isSelfClosing && + base.opts.breakStartBlock && + bbcode.breakStart !== false) || + (bbcode && bbcode.breakStart); + + breakEnd = (isBlock && base.opts.breakEndBlock && + bbcode.breakEnd !== false) || + (bbcode && bbcode.breakEnd); + + breakAfter = (isBlock && base.opts.breakAfterBlock && + bbcode.breakAfter !== false) || + (bbcode && bbcode.breakAfter); + + quoteType = (bbcode ? bbcode.quoteType : null) || + base.opts.quoteType || QuoteType.auto; + + if (!bbcode && token.type === TOKEN_OPEN) { + ret.push(token.val); + + if (token.children) { + ret.push(convertToBBCode(token.children)); + } + + if (token.closing) { + ret.push(token.closing.val); + } + } else if (token.type === TOKEN_OPEN) { + if (breakBefore) { + ret.push('\n'); + } + + // Convert the tag and it's attributes to BBCode + ret.push('[' + token.name); + if (token.attrs) { + if (token.attrs.defaultattr) { + ret.push('=', quote( + token.attrs.defaultattr, + quoteType, + 'defaultattr' + )); + + delete token.attrs.defaultattr; + } + + for (attr in token.attrs) { + if (token.attrs.hasOwnProperty(attr)) { + ret.push(' ', attr, '=', + quote(token.attrs[attr], quoteType, attr)); + } + } + } + ret.push(']'); + + if (breakStart) { + ret.push('\n'); + } + + // Convert the tags children to BBCode + if (token.children) { + ret.push(convertToBBCode(token.children)); + } + + // add closing tag if not self closing + if (!isSelfClosing && !bbcode.excludeClosing) { + if (breakEnd) { + ret.push('\n'); + } + + ret.push('[/' + token.name + ']'); + } + + if (breakAfter) { + ret.push('\n'); + } + + // preserve whatever was recognized as the + // closing tag if it is a self closing tag + if (token.closing && isSelfClosing) { + ret.push(token.closing.val); + } + } else { + ret.push(token.val); + } + } + + return ret.join(''); + } + + /** + * Quotes an attribute + * + * @param {string} str + * @param {BBCodeParser.QuoteType} quoteType + * @param {string} name + * @return {string} + * @private + */ + function quote(str, quoteType, name) { + var needsQuotes = /\s|=/.test(str); + + if (isFunction(quoteType)) { + return quoteType(str, name); + } + + if (quoteType === QuoteType.never || + (quoteType === QuoteType.auto && !needsQuotes)) { + return str; + } + + return '"' + str.replace('\\', '\\\\').replace('"', '\\"') + '"'; + } + + /** + * Returns the last element of an array or null + * + * @param {array} arr + * @return {Object} Last element + * @private + */ + function last(arr) { + if (arr.length) { + return arr[arr.length - 1]; + } + + return null; + } + + /** + * Converts a string to lowercase. + * + * @param {string} str + * @return {string} Lowercase version of str + * @private + */ + function lower(str) { + return str.toLowerCase(); + } + }; + + /** + * Quote type + * @type {Object} + * @class QuoteType + * @name BBCodeParser.QuoteType + * @since v1.4.0 + */ + BBCodeParser.QuoteType = QuoteType; + + /** + * Default BBCode parser options + * @type {Object} + */ + BBCodeParser.defaults = { + /** + * If to add a new line before block level elements + * + * @type {Boolean} + */ + breakBeforeBlock: false, + + /** + * If to add a new line after the start of block level elements + * + * @type {Boolean} + */ + breakStartBlock: false, + + /** + * If to add a new line before the end of block level elements + * + * @type {Boolean} + */ + breakEndBlock: false, + + /** + * If to add a new line after block level elements + * + * @type {Boolean} + */ + breakAfterBlock: true, + + /** + * If to remove empty tags + * + * @type {Boolean} + */ + removeEmptyTags: true, + + /** + * If to fix invalid nesting, + * i.e. block level elements inside inline elements. + * + * @type {Boolean} + */ + fixInvalidNesting: true, + + /** + * If to fix invalid children. + * i.e. A tag which is inside a parent that doesn't + * allow that type of tag. + * + * @type {Boolean} + */ + fixInvalidChildren: true, + + /** + * Attribute quote type + * + * @type {BBCodeParser.QuoteType} + * @since 1.4.1 + */ + quoteType: QuoteType.auto + }; + + /** + * Converts a number 0-255 to hex. + * + * Will return 00 if number is not a valid number. + * + * @param {any} number + * @return {string} + * @private + */ + function toHex(number) { + number = parseInt(number, 10); + + if (isNaN(number)) { + return '00'; + } + + number = Math.max(0, Math.min(number, 255)).toString(16); + + return number.length < 2 ? '0' + number : number; + } + /** + * Normalises a CSS colour to hex #xxxxxx format + * + * @param {string} colorStr + * @return {string} + * @private + */ + function _normaliseColour(colorStr) { + var match; + + colorStr = colorStr || '#000'; + + // rgb(n,n,n); + if ((match = + colorStr.match(/rgb\((\d{1,3}),\s*?(\d{1,3}),\s*?(\d{1,3})\)/i))) { + return '#' + + toHex(match[1]) + + toHex(match[2]) + + toHex(match[3]); + } + + // expand shorthand + if ((match = colorStr.match(/#([0-f])([0-f])([0-f])\s*?$/i))) { + return '#' + + match[1] + match[1] + + match[2] + match[2] + + match[3] + match[3]; + } + + return colorStr; + } + + /** + * SCEditor BBCode format + * @since 2.0.0 + */ + function bbcodeFormat() { + var base = this; + + base.stripQuotes = _stripQuotes; + + /** + * cache of all the tags pointing to their bbcodes to enable + * faster lookup of which bbcode a tag should have + * @private + */ + var tagsToBBCodes = {}; + + /** + * Same as tagsToBBCodes but instead of HTML tags it's styles + * @private + */ + var stylesToBBCodes = {}; + + /** + * Allowed children of specific HTML tags. Empty array if no + * children other than text nodes are allowed + * @private + */ + var validChildren = { + ul: ['li', 'ol', 'ul'], + ol: ['li', 'ol', 'ul'], + table: ['tr'], + tr: ['td', 'th'], + code: ['br', 'p', 'div'] + }; + + /** + * Populates tagsToBBCodes and stylesToBBCodes to enable faster lookups + * + * @private + */ + function buildBbcodeCache() { + each(bbcodeHandlers, function (bbcode) { + var isBlock, + tags = bbcodeHandlers[bbcode].tags, + styles = bbcodeHandlers[bbcode].styles; + + if (tags) { + each(tags, function (tag, values) { + isBlock = bbcodeHandlers[bbcode].isInline === false; + + tagsToBBCodes[tag] = tagsToBBCodes[tag] || {}; + + tagsToBBCodes[tag][isBlock] = + tagsToBBCodes[tag][isBlock] || {}; + + tagsToBBCodes[tag][isBlock][bbcode] = values; + }); + } + + if (styles) { + each(styles, function (style, values) { + isBlock = bbcodeHandlers[bbcode].isInline === false; + + stylesToBBCodes[isBlock] = + stylesToBBCodes[isBlock] || {}; + + stylesToBBCodes[isBlock][style] = + stylesToBBCodes[isBlock][style] || {}; + + stylesToBBCodes[isBlock][style][bbcode] = values; + }); + } + }); + }; + + /** + * Checks if any bbcode styles match the elements styles + * + * @param {!HTMLElement} element + * @param {string} content + * @param {boolean} [blockLevel=false] + * @return {string} Content with any matching + * bbcode tags wrapped around it. + * @private + */ + function handleStyles(element, content, blockLevel) { + var styleValue, format, + getStyle = dom.getStyle; + + // convert blockLevel to boolean + blockLevel = !!blockLevel; + + if (!stylesToBBCodes[blockLevel]) { + return content; + } + + each(stylesToBBCodes[blockLevel], function (property, bbcodes) { + styleValue = getStyle(element, property); + + // if the parent has the same style use that instead of this one + // so you don't end up with [i]parent[i]child[/i][/i] + if (!styleValue || + getStyle(element.parentNode, property) === styleValue) { + return; + } + + each(bbcodes, function (bbcode, values) { + if (!values || values.indexOf(styleValue.toString()) > -1) { + format = bbcodeHandlers[bbcode].format; + + if (isFunction(format)) { + content = format.call(base, element, content); + } else { + content = _formatString(format, content); + } + } + }); + }); + + return content; + } + + /** + * Handles adding newlines after block level elements + * + * @param {HTMLElement} element The element to convert + * @param {string} content The tags text content + * @return {string} + * @private + */ + function handleBlockNewlines(element, content) { + var tag = element.nodeName.toLowerCase(); + var isInline = dom.isInline; + if (!isInline(element, true) || tag === 'br') { + var isLastBlockChild, parent, parentLastChild, + previousSibling = element.previousSibling; + + // Skips selection makers and ignored elements + // Skip empty inline elements + while (previousSibling && + previousSibling.nodeType === 1 && + !is(previousSibling, 'br') && + isInline(previousSibling, true) && + !previousSibling.firstChild) { + previousSibling = previousSibling.previousSibling; + } + + // If it's the last block of an inline that is the last + // child of a block then it shouldn't cause a line break + // except in IE < 11 + //
    + do { + parent = element.parentNode; + parentLastChild = parent && parent.lastChild; + + isLastBlockChild = parentLastChild === element; + element = parent; + } while (parent && isLastBlockChild && isInline(parent, true)); + + // If this block is: + // * Not the last child of a block level element + // * Is a
  • tag (lists are blocks) + // * Is IE < 11 and the tag is BR. IE < 11 never collapses BR + // tags. + if (!isLastBlockChild || tag === 'li' || + (tag === 'br' && IE_BR_FIX)) { + content += '\n'; + } + + // Check for: + // texttext + // + // The second opening opening tag should cause a + // line break because the previous sibing is inline. + if (tag !== 'br' && previousSibling && + !is(previousSibling, 'br') && + isInline(previousSibling, true)) { + content = '\n' + content; + } + } + + return content; + } + + /** + * Handles a HTML tag and finds any matching bbcodes + * + * @param {HTMLElement} element The element to convert + * @param {string} content The Tags text content + * @param {boolean} [blockLevel=false] If to convert block level tags + * @return {string} Content with any matching bbcode tags + * wrapped around it. + * @private + */ + function handleTags(element, content, blockLevel) { + var convertBBCode, format, + tag = element.nodeName.toLowerCase(); + + // convert blockLevel to boolean + blockLevel = !!blockLevel; + + if (tagsToBBCodes[tag] && tagsToBBCodes[tag][blockLevel]) { + // loop all bbcodes for this tag + each(tagsToBBCodes[tag][blockLevel], function ( + bbcode, bbcodeAttribs) { + // if the bbcode requires any attributes then check this has + // all needed + if (bbcodeAttribs) { + convertBBCode = false; + + // loop all the bbcode attribs + each(bbcodeAttribs, function (attrib, values) { + // Skip if the element doesn't have the attibue or + // the attribute doesn't match one of the require + // values + if (!attr(element, attrib) || (values && + values.indexOf(attr(element, attrib)) < 0)) { + return; + } + + // break this loop as we have matched this bbcode + convertBBCode = true; + return false; + }); + + if (!convertBBCode) { + return; + } + } + + format = bbcodeHandlers[bbcode].format; + + if (isFunction(format)) { + content = format.call(base, element, content); + } else { + content = _formatString(format, content); + } + }); + } + + return content; + } + + /** + * Converts a HTML dom element to BBCode starting from + * the innermost element and working backwards + * + * @private + * @param {HTMLElement} element + * @return {string} BBCode + * @memberOf SCEditor.plugins.bbcode.prototype + */ + function elementToBbcode(element) { + var toBBCode = function (node, vChildren) { + var ret = ''; + + dom.traverse(node, function (node) { + var curTag = '', + nodeType = node.nodeType, + tag = node.nodeName.toLowerCase(), + vChild = validChildren[tag], + firstChild = node.firstChild, + isValidChild = true; + + if (typeof vChildren === 'object') { + isValidChild = vChildren.indexOf(tag) > -1; + + // Emoticons should always be converted + if (is(node, 'img') && attr(node, EMOTICON_DATA_ATTR)) { + isValidChild = true; + } + + // if this tag is one of the parents allowed children + // then set this tags allowed children to whatever it + // allows, otherwise set to what the parent allows + if (!isValidChild) { + vChild = vChildren; + } + } + + // 3 = text and 1 = element + if (nodeType !== 3 && nodeType !== 1) { + return; + } + + if (nodeType === 1) { + // skip empty nlf elements (new lines automatically + // added after block level elements like quotes) + if (is(node, '.sceditor-nlf')) { + if (!firstChild || (!IE_BR_FIX && + node.childNodes.length === 1 && + /br/i.test(firstChild.nodeName))) { + return; + } + } + + // don't convert iframe contents + if (tag !== 'iframe') { + curTag = toBBCode(node, vChild); + } + + // TODO: isValidChild is no longer needed. Should use + // valid children bbcodes instead by creating BBCode + // tokens like the parser. + if (isValidChild) { + // code tags should skip most styles + if (tag !== 'code') { + // handle inline bbcodes + curTag = handleStyles(node, curTag); + curTag = handleTags(node, curTag); + + // handle blocklevel bbcodes + curTag = handleStyles(node, curTag, true); + } + + curTag = handleTags(node, curTag, true); + ret += handleBlockNewlines(node, curTag); + } else { + ret += curTag; + } + } else { + ret += node.nodeValue; + } + }, false, true); + + return ret; + }; + + return toBBCode(element); + }; + + /** + * Initializer + * @private + */ + base.init = function () { + base.opts = this.opts; + base.elementToBbcode = elementToBbcode; + + // build the BBCode cache + buildBbcodeCache(); + + this.commands = extend( + true, {}, defaultCommandsOverrides, this.commands + ); + + // Add BBCode helper methods + this.toBBCode = base.toSource; + this.fromBBCode = base.toHtml; + }; + + /** + * Converts BBCode into HTML + * + * @param {boolean} asFragment + * @param {string} source + * @param {boolean} [legacyAsFragment] Used by fromBBCode() method + */ + function toHtml(asFragment, source, legacyAsFragment) { + var parser = new BBCodeParser(base.opts.parserOptions); + var html = parser.toHTML( + base.opts.bbcodeTrim ? source.trim() : source + ); + + return (asFragment || legacyAsFragment) ? + removeFirstLastDiv(html) : html; + } + + /** + * Converts HTML into BBCode + * + * @param {boolean} asFragment + * @param {string} html + * @param {!Document} [context] + * @param {!HTMLElement} [parent] + * @return {string} + * @private + */ + function toSource(asFragment, html, context, parent) { + context = context || document; + + var bbcode, elements; + var containerParent = context.createElement('div'); + var container = context.createElement('div'); + var parser = new BBCodeParser(base.opts.parserOptions); + + container.innerHTML = html; + css(containerParent, 'visibility', 'hidden'); + containerParent.appendChild(container); + context.body.appendChild(containerParent); + + if (asFragment) { + // Add text before and after so removeWhiteSpace doesn't remove + // leading and trailing whitespace + containerParent.insertBefore( + context.createTextNode('#'), + containerParent.firstChild + ); + containerParent.appendChild(context.createTextNode('#')); + } + + // Match parents white-space handling + if (parent) { + css(container, 'whiteSpace', css(parent, 'whiteSpace')); + } + + // Remove all nodes with sceditor-ignore class + elements = container.getElementsByClassName('sceditor-ignore'); + while (elements.length) { + elements[0].parentNode.removeChild(elements[0]); + } + + dom.removeWhiteSpace(containerParent); + + bbcode = elementToBbcode(container); + + context.body.removeChild(containerParent); + + bbcode = parser.toBBCode(bbcode, true); + + if (base.opts.bbcodeTrim) { + bbcode = bbcode.trim(); + } + + return bbcode; + }; + + base.toHtml = toHtml.bind(null, false); + base.fragmentToHtml = toHtml.bind(null, true); + base.toSource = toSource.bind(null, false); + base.fragmentToSource = toSource.bind(null, true); + }; + + /** + * Gets a BBCode + * + * @param {string} name + * @return {Object|null} + * @since 2.0.0 + */ + bbcodeFormat.get = function (name) { + return bbcodeHandlers[name] || null; + }; + + /** + * Adds a BBCode to the parser or updates an existing + * BBCode if a BBCode with the specified name already exists. + * + * @param {string} name + * @param {Object} bbcode + * @return {this} + * @since 2.0.0 + */ + bbcodeFormat.set = function (name, bbcode) { + if (name && bbcode) { + // merge any existing command properties + bbcode = extend(bbcodeHandlers[name] || {}, bbcode); + + bbcode.remove = function () { + delete bbcodeHandlers[name]; + }; + + bbcodeHandlers[name] = bbcode; + } + + return this; + }; + + /** + * Renames a BBCode + * + * This does not change the format or HTML handling, those must be + * changed manually. + * + * @param {string} name [description] + * @param {string} newName [description] + * @return {this|false} + * @since 2.0.0 + */ + bbcodeFormat.rename = function (name, newName) { + if (name in bbcodeHandlers) { + bbcodeHandlers[newName] = bbcodeHandlers[name]; + + delete bbcodeHandlers[name]; + } + + return this; + }; + + /** + * Removes a BBCode + * + * @param {string} name + * @return {this} + * @since 2.0.0 + */ + bbcodeFormat.remove = function (name) { + if (name in bbcodeHandlers) { + delete bbcodeHandlers[name]; + } + + return this; + }; + + bbcodeFormat.formatBBCodeString = formatBBCodeString; + + sceditor.formats.bbcode = bbcodeFormat; + sceditor.BBCodeParser = BBCodeParser; +}(sceditor)); diff --git a/public/assets/development/formats/xhtml.js b/public/assets/development/formats/xhtml.js new file mode 100644 index 0000000..1117998 --- /dev/null +++ b/public/assets/development/formats/xhtml.js @@ -0,0 +1,1248 @@ +/** + * SCEditor XHTML Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (sceditor) { + 'use strict'; + + var IE_VER = sceditor.ie; + + // In IE < 11 a BR at the end of a block level element + // causes a double line break. + var IE_BR_FIX = IE_VER && IE_VER < 11; + + var dom = sceditor.dom; + var utils = sceditor.utils; + + var css = dom.css; + var attr = dom.attr; + var is = dom.is; + var removeAttr = dom.removeAttr; + var convertElement = dom.convertElement; + var extend = utils.extend; + var each = utils.each; + var isEmptyObject = utils.isEmptyObject; + + var getEditorCommand = sceditor.command.get; + + var defaultCommandsOverrides = { + bold: { + txtExec: ['', ''] + }, + italic: { + txtExec: ['', ''] + }, + underline: { + txtExec: ['', ''] + }, + strike: { + txtExec: ['', ''] + }, + subscript: { + txtExec: ['', ''] + }, + superscript: { + txtExec: ['', ''] + }, + left: { + txtExec: ['
    ', '
    '] + }, + center: { + txtExec: ['
    ', '
    '] + }, + right: { + txtExec: ['
    ', '
    '] + }, + justify: { + txtExec: ['
    ', '
    '] + }, + font: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('font')._dropDown( + editor, + caller, + function (font) { + editor.insertText('', ''); + } + ); + } + }, + size: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('size')._dropDown( + editor, + caller, + function (size) { + editor.insertText('', ''); + } + ); + } + }, + color: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('color')._dropDown( + editor, + caller, + function (color) { + editor.insertText('', ''); + } + ); + } + }, + bulletlist: { + txtExec: ['
    • ', '
    '] + }, + orderedlist: { + txtExec: ['
    1. ', '
    '] + }, + table: { + txtExec: ['
    ', '
    '] + }, + horizontalrule: { + txtExec: ['
    '] + }, + code: { + txtExec: ['', ''] + }, + image: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('image')._dropDown( + editor, + caller, + selected, + function (url, width, height) { + var attrs = ''; + + if (width) { + attrs += ' width="' + width + '"'; + } + + if (height) { + attrs += ' height="' + height + '"'; + } + + editor.insertText( + '' + ); + } + ); + } + }, + email: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('email')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '' + + (text || selected || url) + + '' + ); + } + ); + } + }, + link: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('link')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '' + + (text || selected || url) + + '' + ); + } + ); + } + }, + quote: { + txtExec: ['
    ', '
    '] + }, + youtube: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('youtube')._dropDown( + editor, + caller, + function (id, time) { + editor.insertText( + '' + ); + } + ); + } + }, + rtl: { + txtExec: ['
    ', '
    '] + }, + ltr: { + txtExec: ['
    ', '
    '] + } + }; + + /** + * XHTMLSerializer part of the XHTML plugin. + * + * @class XHTMLSerializer + * @name jQuery.sceditor.XHTMLSerializer + * @since v1.4.1 + */ + sceditor.XHTMLSerializer = function () { + var base = this; + + var opts = { + indentStr: '\t' + }; + + /** + * Array containing the output, used as it's faster + * than string concatenation in slow browsers. + * @type {Array} + * @private + */ + var outputStringBuilder = []; + + /** + * Current indention level + * @type {number} + * @private + */ + var currentIndent = 0; + + // TODO: use escape.entities + /** + * Escapes XHTML entities + * + * @param {string} str + * @return {string} + * @private + */ + function escapeEntities(str) { + var entities = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\xa0': ' ' + }; + + return !str ? '' : str.replace(/[&<>"\xa0]/g, function (entity) { + return entities[entity] || entity; + }); + }; + + /** + * @param {string} str + * @return {string} + * @private + */ + function trim(str) { + return str + // New lines will be shown as spaces so just convert to spaces. + .replace(/[\r\n]/, ' ') + .replace(/[^\S|\u00A0]+/g, ' '); + }; + + /** + * Serializes a node to XHTML + * + * @param {Node} node Node to serialize + * @param {boolean} onlyChildren If to only serialize the nodes + * children and not the node + * itself + * @return {string} The serialized node + * @name serialize + * @memberOf jQuery.sceditor.XHTMLSerializer.prototype + * @since v1.4.1 + */ + base.serialize = function (node, onlyChildren) { + outputStringBuilder = []; + + if (onlyChildren) { + node = node.firstChild; + + while (node) { + serializeNode(node); + node = node.nextSibling; + } + } else { + serializeNode(node); + } + + return outputStringBuilder.join(''); + }; + + /** + * Serializes a node to the outputStringBuilder + * + * @param {Node} node + * @return {void} + * @private + */ + function serializeNode(node, parentIsPre) { + switch (node.nodeType) { + case 1: // element + var tagName = node.nodeName.toLowerCase(); + + // IE comment + if (tagName === '!') { + handleComment(node); + } else { + handleElement(node, parentIsPre); + } + break; + + case 3: // text + handleText(node, parentIsPre); + break; + + case 4: // cdata section + handleCdata(node); + break; + + case 8: // comment + handleComment(node); + break; + + case 9: // document + case 11: // document fragment + handleDoc(node); + break; + + // Ignored types + case 2: // attribute + case 5: // entity ref + case 6: // entity + case 7: // processing instruction + case 10: // document type + case 12: // notation + break; + } + }; + + /** + * Handles doc node + * @param {Node} node + * @return {void} + * @private + */ + function handleDoc(node) { + var child = node.firstChild; + + while (child) { + serializeNode(child); + child = child.nextSibling; + } + }; + + /** + * Handles element nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleElement(node, parentIsPre) { + var child, attr, attrValue, + tagName = node.nodeName.toLowerCase(), + isIframe = tagName === 'iframe', + attrIdx = node.attributes.length, + firstChild = node.firstChild, + // pre || pre-wrap with any vendor prefix + isPre = parentIsPre || + /pre(?:\-wrap)?$/i.test(css(node, 'whiteSpace')), + selfClosing = !node.firstChild && !dom.canHaveChildren(node) && + !isIframe; + + if (is(node, '.sceditor-ignore')) { + return; + } + + output('<' + tagName, !parentIsPre && canIndent(node)); + while (attrIdx--) { + attr = node.attributes[attrIdx]; + + attrValue = attr.value; + + output(' ' + attr.name.toLowerCase() + '="' + + escapeEntities(attrValue) + '"', false); + } + output(selfClosing ? ' />' : '>', false); + + if (!isIframe) { + child = firstChild; + } + + while (child) { + currentIndent++; + + serializeNode(child, isPre); + child = child.nextSibling; + + currentIndent--; + } + + if (!selfClosing) { + output( + '', + !isPre && !isIframe && canIndent(node) && + firstChild && canIndent(firstChild) + ); + } + }; + + /** + * Handles CDATA nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleCdata(node) { + output(''); + }; + + /** + * Handles comment nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleComment(node) { + output(''); + }; + + /** + * Handles text nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleText(node, parentIsPre) { + var text = node.nodeValue; + + if (!parentIsPre) { + text = trim(text); + } + + if (text) { + output(escapeEntities(text), !parentIsPre && canIndent(node)); + } + }; + + /** + * Adds a string to the outputStringBuilder. + * + * The string will be indented unless indent is set to boolean false. + * @param {string} str + * @param {boolean} indent + * @return {void} + * @private + */ + function output(str, indent) { + var i = currentIndent; + + if (indent !== false) { + // Don't add a new line if it's the first element + if (outputStringBuilder.length) { + outputStringBuilder.push('\n'); + } + + while (i--) { + outputStringBuilder.push(opts.indentStr); + } + } + + outputStringBuilder.push(str); + }; + + /** + * Checks if should indent the node or not + * @param {Node} node + * @return {boolean} + * @private + */ + function canIndent(node) { + var prev = node.previousSibling; + + if (node.nodeType !== 1 && prev) { + return !dom.isInline(prev); + } + + // first child of a block element + if (!prev && !dom.isInline(node.parentNode)) { + return true; + } + + return !dom.isInline(node); + }; + }; + + /** + * SCEditor XHTML plugin + * @class xhtml + * @name jQuery.sceditor.plugins.xhtml + * @since v1.4.1 + */ + function xhtmlFormat() { + var base = this; + + /** + * Tag converters cache + * @type {Object} + * @private + */ + var tagConvertersCache = {}; + + /** + * Attributes filter cache + * @type {Object} + * @private + */ + var attrsCache = {}; + + /** + * Init + * @return {void} + */ + base.init = function () { + if (!isEmptyObject(xhtmlFormat.converters || {})) { + each( + xhtmlFormat.converters, + function (idx, converter) { + each(converter.tags, function (tagname) { + if (!tagConvertersCache[tagname]) { + tagConvertersCache[tagname] = []; + } + + tagConvertersCache[tagname].push(converter); + }); + } + ); + } + + this.commands = extend(true, + {}, defaultCommandsOverrides, this.commands); + }; + + /** + * Converts the WYSIWYG content to XHTML + * + * @param {boolean} isFragment + * @param {string} html + * @param {Document} context + * @param {HTMLElement} [parent] + * @return {string} + * @memberOf jQuery.sceditor.plugins.xhtml.prototype + */ + function toSource(isFragment, html, context) { + var xhtml, + container = context.createElement('div'); + container.innerHTML = html; + + css(container, 'visibility', 'hidden'); + context.body.appendChild(container); + + convertTags(container); + removeTags(container); + removeAttribs(container); + + if (!isFragment) { + wrapInlines(container); + } + + xhtml = (new sceditor.XHTMLSerializer()).serialize(container, true); + + context.body.removeChild(container); + + return xhtml; + }; + + base.toSource = toSource.bind(null, false); + + base.fragmentToSource = toSource.bind(null, true);; + + /** + * Runs all converters for the specified tagName + * against the DOM node. + * @param {string} tagName + * @return {Node} node + * @private + */ + function convertNode(tagName, node) { + if (!tagConvertersCache[tagName]) { + return; + } + + tagConvertersCache[tagName].forEach(function (converter) { + if (converter.tags[tagName]) { + each(converter.tags[tagName], function (attr, values) { + if (!node.getAttributeNode) { + return; + } + + attr = node.getAttributeNode(attr); + + if (!attr || values && values.indexOf(attr.value) < 0) { + return; + } + + converter.conv.call(base, node); + }); + } else if (converter.conv) { + converter.conv.call(base, node); + } + }); + }; + + /** + * Converts any tags/attributes to their XHTML equivalents + * @param {Node} node + * @return {void} + * @private + */ + function convertTags(node) { + dom.traverse(node, function (node) { + var tagName = node.nodeName.toLowerCase(); + + convertNode('*', node); + convertNode(tagName, node); + }, true); + }; + + /** + * Tests if a node is empty and can be removed. + * + * @param {Node} node + * @return {boolean} + * @private + */ + function isEmpty(node, excludeBr) { + var rect, + childNodes = node.childNodes, + tagName = node.nodeName.toLowerCase(), + nodeValue = node.nodeValue, + childrenLength = childNodes.length, + allowedEmpty = xhtmlFormat.allowedEmptyTags || []; + + if (excludeBr && tagName === 'br') { + return true; + } + + if (is(node, '.sceditor-ignore')) { + return true; + } + + if (allowedEmpty.indexOf(tagName) > -1 || tagName === 'td' || + !dom.canHaveChildren(node)) { + + return false; + } + + // \S|\u00A0 = any non space char + if (nodeValue && /\S|\u00A0/.test(nodeValue)) { + return false; + } + + while (childrenLength--) { + if (!isEmpty(childNodes[childrenLength], + excludeBr && !node.previousSibling && !node.nextSibling)) { + return false; + } + } + + // Treat tags with a width and height from CSS as not empty + if (node.getBoundingClientRect && + (node.className || node.hasAttributes('style'))) { + rect = node.getBoundingClientRect(); + return !rect.width || !rect.height; + } + + return true; + }; + + /** + * Removes any tags that are not white listed or if no + * tags are white listed it will remove any tags that + * are black listed. + * + * @param {Node} rootNode + * @return {void} + * @private + */ + function removeTags(rootNode) { + dom.traverse(rootNode, function (node) { + var remove, + tagName = node.nodeName.toLowerCase(), + parentNode = node.parentNode, + nodeType = node.nodeType, + isBlock = !dom.isInline(node), + previousSibling = node.previousSibling, + nextSibling = node.nextSibling, + isTopLevel = parentNode === rootNode, + noSiblings = !previousSibling && !nextSibling, + empty = tagName !== 'iframe' && isEmpty(node, + isTopLevel && noSiblings && tagName !== 'br'), + document = node.ownerDocument, + allowedTags = xhtmlFormat.allowedTags, + firstChild = node.firstChild, + disallowedTags = xhtmlFormat.disallowedTags; + + // 3 = text node + if (nodeType === 3) { + return; + } + + if (nodeType === 4) { + tagName = '!cdata'; + } else if (tagName === '!' || nodeType === 8) { + tagName = '!comment'; + } + + if (nodeType === 1) { + // skip empty nlf elements (new lines automatically + // added after block level elements like quotes) + if (is(node, '.sceditor-nlf')) { + if (!firstChild || (!IE_BR_FIX && + node.childNodes.length === 1 && + /br/i.test(firstChild.nodeName))) { + // Mark as empty,it will be removed by the next code + empty = true; + } else { + node.classList.remove('sceditor-nlf'); + + if (!node.className) { + removeAttr(node, 'class'); + } + } + } + } + + if (empty) { + remove = true; + // 3 is text node which do not get filtered + } else if (allowedTags && allowedTags.length) { + remove = (allowedTags.indexOf(tagName) < 0); + } else if (disallowedTags && disallowedTags.length) { + remove = (disallowedTags.indexOf(tagName) > -1); + } + + if (remove) { + if (!empty) { + if (isBlock && previousSibling && + dom.isInline(previousSibling)) { + parentNode.insertBefore( + document.createTextNode(' '), node); + } + + // Insert all the childen after node + while (node.firstChild) { + parentNode.insertBefore(node.firstChild, + nextSibling); + } + + if (isBlock && nextSibling && + dom.isInline(nextSibling)) { + parentNode.insertBefore( + document.createTextNode(' '), nextSibling); + } + } + + parentNode.removeChild(node); + } + }, true); + }; + + /** + * Merges two sets of attribute filters into one + * + * @param {Object} filtersA + * @param {Object} filtersB + * @return {Object} + * @private + */ + function mergeAttribsFilters(filtersA, filtersB) { + var ret = {}; + + if (filtersA) { + extend(ret, filtersA); + } + + if (!filtersB) { + return ret; + } + + each(filtersB, function (attrName, values) { + if (Array.isArray(values)) { + ret[attrName] = (ret[attrName] || []).concat(values); + } else if (!ret[attrName]) { + ret[attrName] = null; + } + }); + + return ret; + }; + + /** + * Wraps adjacent inline child nodes of root + * in paragraphs. + * + * @param {Node} root + * @private + */ + function wrapInlines(root) { + // Strip empty text nodes so they don't get wrapped. + dom.removeWhiteSpace(root); + + var wrapper; + var node = root.firstChild; + var next; + while (node) { + next = node.nextSibling; + + if (dom.isInline(node) && !is(node, '.sceditor-ignore')) { + if (!wrapper) { + wrapper = root.ownerDocument.createElement('p'); + node.parentNode.insertBefore(wrapper, node); + } + + wrapper.appendChild(node); + } else { + wrapper = null; + } + + node = next; + } + }; + + /** + * Removes any attributes that are not white listed or + * if no attributes are white listed it will remove + * any attributes that are black listed. + * @param {Node} node + * @return {void} + * @private + */ + function removeAttribs(node) { + var tagName, attr, attrName, attrsLength, validValues, remove, + allowedAttribs = xhtmlFormat.allowedAttribs, + isAllowed = allowedAttribs && + !isEmptyObject(allowedAttribs), + disallowedAttribs = xhtmlFormat.disallowedAttribs, + isDisallowed = disallowedAttribs && + !isEmptyObject(disallowedAttribs); + + attrsCache = {}; + + dom.traverse(node, function (node) { + if (!node.attributes) { + return; + } + + tagName = node.nodeName.toLowerCase(); + attrsLength = node.attributes.length; + + if (attrsLength) { + if (!attrsCache[tagName]) { + if (isAllowed) { + attrsCache[tagName] = mergeAttribsFilters( + allowedAttribs['*'], + allowedAttribs[tagName] + ); + } else { + attrsCache[tagName] = mergeAttribsFilters( + disallowedAttribs['*'], + disallowedAttribs[tagName] + ); + } + } + + while (attrsLength--) { + attr = node.attributes[attrsLength]; + attrName = attr.name; + validValues = attrsCache[tagName][attrName]; + remove = false; + + if (isAllowed) { + remove = validValues !== null && + (!Array.isArray(validValues) || + validValues.indexOf(attr.value) < 0); + } else if (isDisallowed) { + remove = validValues === null || + (Array.isArray(validValues) && + validValues.indexOf(attr.value) > -1); + } + + if (remove) { + node.removeAttribute(attrName); + } + } + } + }); + }; + }; + + /** + * Tag conveters, a converter is applied to all + * tags that match the criteria. + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.converters + * @since v1.4.1 + */ + xhtmlFormat.converters = [ + { + tags: { + '*': { + width: null + } + }, + conv: function (node) { + css(node, 'width', attr(node, 'width')); + removeAttr(node, 'width'); + } + }, + { + tags: { + '*': { + height: null + } + }, + conv: function (node) { + css(node, 'height', attr(node, 'height')); + removeAttr(node, 'height'); + } + }, + { + tags: { + 'li': { + value: null + } + }, + conv: function (node) { + removeAttr(node, 'value'); + } + }, + { + tags: { + '*': { + text: null + } + }, + conv: function (node) { + css(node, 'color', attr(node, 'text')); + removeAttr(node, 'text'); + } + }, + { + tags: { + '*': { + color: null + } + }, + conv: function (node) { + css(node, 'color', attr(node, 'color')); + removeAttr(node, 'color'); + } + }, + { + tags: { + '*': { + face: null + } + }, + conv: function (node) { + css(node, 'fontFamily', attr(node, 'face')); + removeAttr(node, 'face'); + } + }, + { + tags: { + '*': { + align: null + } + }, + conv: function (node) { + css(node, 'textAlign', attr(node, 'align')); + removeAttr(node, 'align'); + } + }, + { + tags: { + '*': { + border: null + } + }, + conv: function (node) { + css(node, 'borderWidth', attr(node, 'border')); + removeAttr(node, 'border'); + } + }, + { + tags: { + applet: { + name: null + }, + img: { + name: null + }, + layer: { + name: null + }, + map: { + name: null + }, + object: { + name: null + }, + param: { + name: null + } + }, + conv: function (node) { + if (!attr(node, 'id')) { + attr(node, 'id', attr(node, 'name')); + } + + removeAttr(node, 'name'); + } + }, + { + tags: { + '*': { + vspace: null + } + }, + conv: function (node) { + css(node, 'marginTop', attr(node, 'vspace') - 0); + css(node, 'marginBottom', attr(node, 'vspace') - 0); + removeAttr(node, 'vspace'); + } + }, + { + tags: { + '*': { + hspace: null + } + }, + conv: function (node) { + css(node, 'marginLeft', attr(node, 'hspace') - 0); + css(node, 'marginRight', attr(node, 'hspace') - 0); + removeAttr(node, 'hspace'); + } + }, + { + tags: { + 'hr': { + noshade: null + } + }, + conv: function (node) { + css(node, 'borderStyle', 'solid'); + removeAttr(node, 'noshade'); + } + }, + { + tags: { + '*': { + nowrap: null + } + }, + conv: function (node) { + css(node, 'whiteSpace', 'nowrap'); + removeAttr(node, 'nowrap'); + } + }, + { + tags: { + big: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'fontSize', 'larger'); + } + }, + { + tags: { + small: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'fontSize', 'smaller'); + } + }, + { + tags: { + b: null + }, + conv: function (node) { + convertElement(node, 'strong'); + } + }, + { + tags: { + u: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'textDecoration', + 'underline'); + } + }, + { + tags: { + s: null, + strike: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'textDecoration', + 'line-through'); + } + }, + { + tags: { + dir: null + }, + conv: function (node) { + convertElement(node, 'ul'); + } + }, + { + tags: { + center: null + }, + conv: function (node) { + css(convertElement(node, 'div'), 'textAlign', 'center'); + } + }, + { + tags: { + font: { + size: null + } + }, + conv: function (node) { + css(node, 'fontSize', css(node, 'fontSize')); + removeAttr(node, 'size'); + } + }, + { + tags: { + font: null + }, + conv: function (node) { + // All it's attributes will be converted + // by the attribute converters + convertElement(node, 'span'); + } + }, + { + tags: { + '*': { + type: ['_moz'] + } + }, + conv: function (node) { + removeAttr(node, 'type'); + } + }, + { + tags: { + '*': { + '_moz_dirty': null + } + }, + conv: function (node) { + removeAttr(node, '_moz_dirty'); + } + }, + { + tags: { + '*': { + '_moz_editor_bogus_node': null + } + }, + conv: function (node) { + node.parentNode.removeChild(node); + } + } + ]; + + /** + * Allowed attributes map. + * + * To allow an attribute for all tags use * as the tag name. + * + * Leave empty or null to allow all attributes. (the disallow + * list will be used to filter them instead) + * @type {Object} + * @name jQuery.sceditor.plugins.xhtml.allowedAttribs + * @since v1.4.1 + */ + xhtmlFormat.allowedAttribs = {}; + + /** + * Attributes that are not allowed. + * + * Only used if allowed attributes is null or empty. + * @type {Object} + * @name jQuery.sceditor.plugins.xhtml.disallowedAttribs + * @since v1.4.1 + */ + xhtmlFormat.disallowedAttribs = {}; + + /** + * Array containing all the allowed tags. + * + * If null or empty all tags will be allowed. + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.allowedTags + * @since v1.4.1 + */ + xhtmlFormat.allowedTags = []; + + /** + * Array containing all the disallowed tags. + * + * Only used if allowed tags is null or empty. + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.disallowedTags + * @since v1.4.1 + */ + xhtmlFormat.disallowedTags = []; + + /** + * Array containing tags which should not be removed when empty. + * + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.allowedEmptyTags + * @since v2.0.0 + */ + xhtmlFormat.allowedEmptyTags = []; + + sceditor.formats.xhtml = xhtmlFormat; +}(sceditor)); diff --git a/public/assets/development/icons/material.js b/public/assets/development/icons/material.js new file mode 100644 index 0000000..9c464ee --- /dev/null +++ b/public/assets/development/icons/material.js @@ -0,0 +1,139 @@ +/** + * SCEditor SVG material icons plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (document, sceditor) { + 'use strict'; + + var dom = sceditor.dom; + + /** + * Material icons by Google (Apache license) + * https://github.com/google/material-design-icons/blob/master/LICENSE + * + * Extra icons by materialdesignicons.com and contributors (MIT license) + * https://github.com/Templarian/MaterialDesign-SVG/blob/master/LICENSE + */ + /* eslint max-len: off*/ + var icons = { + 'bold': '', + 'bulletlist': '', + 'center': '', + // Cody @XT3000 - https://materialdesignicons.com/ + 'code': '', + 'color': '', + 'copy': '', + 'cut': '', + 'date': '', + 'email': '', + 'emoticon': '', + // JapanYoshi @japanyoshilol - https://materialdesignicons.com/ + 'font': '', + 'format': '', + // Austin Andrews @Templarian - https://materialdesignicons.com/ + 'grip': '', + // Sam Clarke @samclarke + 'horizontalrule': '', + 'image': '', + 'indent': '', + 'italic': '', + 'justify': ' ', + 'left': ' ', + 'link': '', + 'ltr': '', + // Austin Andrews @Templarian - https://materialdesignicons.com/ + 'maximize': '', + 'orderedlist': '', + 'outdent': '', + 'paste': '', + 'pastetext': '', + 'print': '', + 'quote': '', + 'redo': '', + 'removeformat': '', + 'right': '', + 'rtl': '', + 'size': '', + 'source': '', + 'strike': '', + // Austin Andrews @Templarian - https://materialdesignicons.com/ + 'subscript': '', + // Austin Andrews @Templarian - https://materialdesignicons.com/ + 'superscript': '', + // Austin Andrews @Templarian - https://materialdesignicons.com/ + 'table': '', + 'time': '', + 'underline': '', + 'undo': '', + // Austin Andrews @Templarian - https://materialdesignicons.com/ + 'unlink': '', + 'youtube': '' + }; + + sceditor.icons.material = function () { + var nodes = {}; + + var colorPath; + + return { + create: function (command) { + if (command in icons) { + // Using viewbox="1 1 22 22" to trim off the 1 unit border + // around the SVG icons. + // Default is viewbox="0 0 24 24" + nodes[command] = sceditor.dom.parseHTML( + '' + + icons[command] + + '' + ).firstChild; + + if (command === 'color') { + colorPath = nodes[command].querySelector('.sce-color'); + } + } + + return nodes[command]; + }, + update: function (isSourceMode, currentNode) { + if (colorPath) { + var color = 'inherit'; + + if (!isSourceMode && currentNode) { + color = currentNode.ownerDocument + .queryCommandValue('forecolor'); + + // Needed for IE + if (parseInt(color) === color) { + // eslint-disable-next-line + color = ((color & 0x0000ff) << 16) | (color & 0x00ff00) | ((color & 0xff0000) >>> 16); + color = '#' + ('000000' + color.toString(16)).slice(-6); + } + } + + dom.css(colorPath, 'fill', color); + } + }, + rtl: function (isRtl) { + var gripNode = nodes.grip; + + if (gripNode) { + var transform = isRtl ? 'scaleX(-1)' : ''; + + dom.css(gripNode, 'transform', transform); + dom.css(gripNode, 'msTransform', transform); + dom.css(gripNode, 'webkitTransform', transform); + } + } + }; + }; + + sceditor.icons.material.icons = icons; +})(document, sceditor); diff --git a/public/assets/development/icons/monocons.js b/public/assets/development/icons/monocons.js new file mode 100644 index 0000000..0e852ca --- /dev/null +++ b/public/assets/development/icons/monocons.js @@ -0,0 +1,119 @@ +/** + * SCEditor SVG monocons plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (document, sceditor) { + 'use strict'; + + var dom = sceditor.dom; + + /* eslint max-len: off*/ + var icons = { + 'bold': 'B', + 'bulletlist': '', + 'center': '', + 'code': '', + 'color': 'A', + 'copy': '', + 'cut': '', + 'date': '', + 'email': '', + 'emoticon': '', + 'font': '', + 'format': '', + 'grip': '', + 'horizontalrule': '', + 'image': '', + 'indent': '', + 'italic': 'i', + 'justify': '', + 'left': '', + 'link': '', + 'ltr': '', + 'maximize': '', + 'orderedlist': '', + 'outdent': '', + 'paste': '', + 'pastetext': '', + 'print': '', + 'quote': '', + 'redo': '', + 'removeformat': '', + 'right': '', + 'rtl': '', + 'size': '', + 'source': '', + 'strike': 'S', + 'subscript': '', + 'superscript': '', + 'table': '', + 'time': '', + 'underline': 'U', + 'undo': '', + 'unlink': '', + 'youtube': '' + }; + + sceditor.icons.monocons = function () { + var nodes = {}; + var colorPath; + + return { + create: function (command) { + if (command in icons) { + nodes[command] = sceditor.dom.parseHTML( + '' + + icons[command] + + '' + ).firstChild; + + if (command === 'color') { + colorPath = nodes[command].querySelector('.sce-color'); + } + } + + return nodes[command]; + }, + update: function (isSourceMode, currentNode) { + if (colorPath) { + var color = 'inherit'; + + if (!isSourceMode && currentNode) { + color = currentNode.ownerDocument + .queryCommandValue('forecolor'); + + // Needed for IE + if (parseInt(color) === color) { + // eslint-disable-next-line + color = ((color & 0x0000ff) << 16) | (color & 0x00ff00) | ((color & 0xff0000) >>> 16); + color = '#' + ('000000' + color.toString(16)).slice(-6); + } + } + + dom.css(colorPath, 'fill', color); + } + }, + rtl: function (isRtl) { + var gripNode = nodes.grip; + + if (gripNode) { + var transform = isRtl ? 'scaleX(-1)' : ''; + + dom.css(gripNode, 'transform', transform); + dom.css(gripNode, 'msTransform', transform); + dom.css(gripNode, 'webkitTransform', transform); + } + } + }; + }; + + sceditor.icons.monocons.icons = icons; +})(document, sceditor); diff --git a/public/assets/development/jquery.sceditor.bbcode.js b/public/assets/development/jquery.sceditor.bbcode.js new file mode 100644 index 0000000..8e0354a --- /dev/null +++ b/public/assets/development/jquery.sceditor.bbcode.js @@ -0,0 +1,10372 @@ +(function ($) { + 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + + /** + * Check if the passed argument is the + * the passed type. + * + * @param {string} type + * @param {*} arg + * @returns {boolean} + */ + function isTypeof(type, arg) { + return typeof arg === type; + } + + /** + * @type {function(*): boolean} + */ + var isString = isTypeof.bind(null, 'string'); + + /** + * @type {function(*): boolean} + */ + var isUndefined = isTypeof.bind(null, 'undefined'); + + /** + * @type {function(*): boolean} + */ + var isFunction = isTypeof.bind(null, 'function'); + + /** + * @type {function(*): boolean} + */ + var isNumber = isTypeof.bind(null, 'number'); + + + /** + * Returns true if an object has no keys + * + * @param {!Object} obj + * @returns {boolean} + */ + function isEmptyObject(obj) { + return !Object.keys(obj).length; + } + + /** + * Extends the first object with any extra objects passed + * + * If the first argument is boolean and set to true + * it will extend child arrays and objects recursively. + * + * @param {!Object|boolean} targetArg + * @param {...Object} source + * @return {Object} + */ + function extend(targetArg, sourceArg) { + var isTargetBoolean = targetArg === !!targetArg; + var i = isTargetBoolean ? 2 : 1; + var target = isTargetBoolean ? sourceArg : targetArg; + var isDeep = isTargetBoolean ? targetArg : false; + + for (; i < arguments.length; i++) { + var source = arguments[i]; + + // Copy all properties for jQuery compatibility + /* eslint guard-for-in: off */ + for (var key in source) { + var value = source[key]; + + // Skip undefined values to match jQuery and + // skip if target to prevent infinite loop + if (!isUndefined(value)) { + var isObject = value !== null && typeof value === 'object' && + Object.getPrototypeOf(value) === Object.prototype; + var isArray = Array.isArray(value); + + if (isDeep && (isObject || isArray)) { + target[key] = extend( + true, + target[key] || (isArray ? [] : {}), + value + ); + } else { + target[key] = value; + } + } + } + } + + return target; + } + + /** + * Removes an item from the passed array + * + * @param {!Array} arr + * @param {*} item + */ + function arrayRemove(arr, item) { + var i = arr.indexOf(item); + + if (i > -1) { + arr.splice(i, 1); + } + } + + /** + * Iterates over an array or object + * + * @param {!Object|Array} obj + * @param {function(*, *)} fn + */ + function each(obj, fn) { + if (Array.isArray(obj) || 'length' in obj && isNumber(obj.length)) { + for (var i = 0; i < obj.length; i++) { + fn(i, obj[i]); + } + } else { + Object.keys(obj).forEach(function (key) { + fn(key, obj[key]); + }); + } + } + + /** + * Cache of camelCase CSS property names + * @type {Object} + */ + var cssPropertyNameCache = {}; + + /** + * Node type constant for element nodes + * + * @type {number} + */ + var ELEMENT_NODE = 1; + + /** + * Node type constant for text nodes + * + * @type {number} + */ + var TEXT_NODE = 3; + + /** + * Node type constant for comment nodes + * + * @type {number} + */ + + + /** + * Node type document nodes + * + * @type {number} + */ + + + /** + * Node type constant for document fragments + * + * @type {number} + */ + + + function toFloat(value) { + value = parseFloat(value); + + return isFinite(value) ? value : 0; + } + + /** + * Creates an element with the specified attributes + * + * Will create it in the current document unless context + * is specified. + * + * @param {!string} tag + * @param {!Object} [attributes] + * @param {!Document} [context] + * @returns {!HTMLElement} + */ + function createElement(tag, attributes, context) { + var node = (context || document).createElement(tag); + + each(attributes || {}, function (key, value) { + if (key === 'style') { + node.style.cssText = value; + } else if (key in node) { + node[key] = value; + } else { + node.setAttribute(key, value); + } + }); + + return node; + } + + /** + * Returns an array of parents that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {Array} + */ + + + /** + * Gets the first parent node that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {HTMLElement|undefined} + */ + function parent(node, selector) { + var parent = node || {}; + + while ((parent = parent.parentNode) && !/(9|11)/.test(parent.nodeType)) { + if (!selector || is(parent, selector)) { + return parent; + } + } + } + + /** + * Checks the passed node and all parents and + * returns the first matching node if any. + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {HTMLElement|undefined} + */ + function closest(node, selector) { + return is(node, selector) ? node : parent(node, selector); + } + + /** + * Removes the node from the DOM + * + * @param {!HTMLElement} node + */ + function remove(node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + + /** + * Appends child to parent node + * + * @param {!HTMLElement} node + * @param {!HTMLElement} child + */ + function appendChild(node, child) { + node.appendChild(child); + } + + /** + * Finds any child nodes that match the selector + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {NodeList} + */ + function find(node, selector) { + return node.querySelectorAll(selector); + } + + /** + * For on() and off() if to add/remove the event + * to the capture phase + * + * @type {boolean} + */ + var EVENT_CAPTURE = true; + + /** + * For on() and off() if to add/remove the event + * to the bubble phase + * + * @type {boolean} + */ + + + /** + * Adds an event listener for the specified events. + * + * Events should be a space separated list of events. + * + * If selector is specified the handler will only be + * called when the event target matches the selector. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see off() + */ + // eslint-disable-next-line max-params + function on(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector] || function (e) { + var target = e.target; + while (target && target !== node) { + if (is(target, selector)) { + fn.call(target, e); + return; + } + + target = target.parentNode; + } + }; + + fn['_sce-event-' + event + selector] = handler; + } else { + handler = selector; + capture = fn; + } + + node.addEventListener(event, handler, capture || false); + }); + } + + /** + * Removes an event listener for the specified events. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see on() + */ + // eslint-disable-next-line max-params + function off(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector]; + } else { + handler = selector; + capture = fn; + } + + node.removeEventListener(event, handler, capture || false); + }); + } + + /** + * If only attr param is specified it will get + * the value of the attr param. + * + * If value is specified but null the attribute + * will be removed otherwise the attr value will + * be set to the passed value. + * + * @param {!HTMLElement} node + * @param {!string} attr + * @param {?string} [value] + */ + function attr(node, attr, value) { + if (arguments.length < 3) { + return node.getAttribute(attr); + } + + // eslint-disable-next-line eqeqeq, no-eq-null + if (value == null) { + removeAttr(node, attr); + } else { + node.setAttribute(attr, value); + } + } + + /** + * Removes the specified attribute + * + * @param {!HTMLElement} node + * @param {!string} attr + */ + function removeAttr(node, attr) { + node.removeAttribute(attr); + } + + /** + * Sets the passed elements display to none + * + * @param {!HTMLElement} node + */ + function hide(node) { + css(node, 'display', 'none'); + } + + /** + * Sets the passed elements display to default + * + * @param {!HTMLElement} node + */ + function show(node) { + css(node, 'display', ''); + } + + /** + * Toggles an elements visibility + * + * @param {!HTMLElement} node + */ + function toggle(node) { + if (isVisible(node)) { + hide(node); + } else { + show(node); + } + } + + /** + * Gets a computed CSS values or sets an inline CSS value + * + * Rules should be in camelCase format and not + * hyphenated like CSS properties. + * + * @param {!HTMLElement} node + * @param {!Object|string} rule + * @param {string|number} [value] + * @return {string|number|undefined} + */ + function css(node, rule, value) { + if (arguments.length < 3) { + if (isString(rule)) { + return node.nodeType === 1 ? getComputedStyle(node)[rule] : null; + } + + each(rule, function (key, value) { + css(node, key, value); + }); + } else { + // isNaN returns false for null, false and empty strings + // so need to check it's truthy or 0 + var isNumeric = (value || value === 0) && !isNaN(value); + node.style[rule] = isNumeric ? value + 'px' : value; + } + } + + + /** + * Gets or sets thee data attributes on a node + * + * Unlike the jQuery version this only stores data + * in the DOM attributes which means only strings + * can be stored. + * + * @param {Node} node + * @param {string} [key] + * @param {string} [value] + * @return {Object|undefined} + */ + function data(node, key, value) { + var argsLength = arguments.length; + var data = {}; + + if (node.nodeType === ELEMENT_NODE) { + if (argsLength === 1) { + each(node.attributes, function (_, attr) { + if (/^data\-/i.test(attr.name)) { + data[attr.name.substr(5)] = attr.value; + } + }); + + return data; + } + + if (argsLength === 2) { + return attr(node, 'data-' + key); + } + + attr(node, 'data-' + key, String(value)); + } + } + + /** + * Checks if node matches the given selector. + * + * @param {?HTMLElement} node + * @param {string} selector + * @returns {boolean} + */ + function is(node, selector) { + var result = false; + + if (node && node.nodeType === ELEMENT_NODE) { + result = (node.matches || node.msMatchesSelector || + node.webkitMatchesSelector).call(node, selector); + } + + return result; + } + + + /** + * Returns true if node contains child otherwise false. + * + * This differs from the DOM contains() method in that + * if node and child are equal this will return false. + * + * @param {!Node} node + * @param {HTMLElement} child + * @returns {boolean} + */ + function contains(node, child) { + return node !== child && node.contains && node.contains(child); + } + + /** + * @param {Node} node + * @param {string} [selector] + * @returns {?HTMLElement} + */ + function previousElementSibling(node, selector) { + var prev = node.previousElementSibling; + + if (selector && prev) { + return is(prev, selector) ? prev : null; + } + + return prev; + } + + /** + * @param {!Node} node + * @param {!Node} refNode + * @returns {Node} + */ + function insertBefore(node, refNode) { + return refNode.parentNode.insertBefore(node, refNode); + } + + /** + * @param {?HTMLElement} node + * @returns {!Array.} + */ + function classes(node) { + return node.className.trim().split(/\s+/); + } + + /** + * @param {?HTMLElement} node + * @param {string} className + * @returns {boolean} + */ + function hasClass(node, className) { + return is(node, '.' + className); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function addClass(node, className) { + var classList = classes(node); + + if (classList.indexOf(className) < 0) { + classList.push(className); + } + + node.className = classList.join(' '); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function removeClass(node, className) { + var classList = classes(node); + + arrayRemove(classList, className); + + node.className = classList.join(' '); + } + + /** + * Toggles a class on node. + * + * If state is specified and is truthy it will add + * the class. + * + * If state is specified and is falsey it will remove + * the class. + * + * @param {HTMLElement} node + * @param {string} className + * @param {boolean} [state] + */ + function toggleClass(node, className, state) { + state = isUndefined(state) ? !hasClass(node, className) : state; + + if (state) { + addClass(node, className); + } else { + removeClass(node, className); + } + } + + /** + * Gets or sets the width of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function width(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingLeft) + toFloat(cs.paddingRight); + var border = toFloat(cs.borderLeftWidth) + toFloat(cs.borderRightWidth); + + return node.offsetWidth - padding - border; + } + + css(node, 'width', value); + } + + /** + * Gets or sets the height of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function height(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingTop) + toFloat(cs.paddingBottom); + var border = toFloat(cs.borderTopWidth) + toFloat(cs.borderBottomWidth); + + return node.offsetHeight - padding - border; + } + + css(node, 'height', value); + } + + /** + * Triggers a custom event with the specified name and + * sets the detail property to the data object passed. + * + * @param {HTMLElement} node + * @param {string} eventName + * @param {Object} [data] + */ + function trigger(node, eventName, data) { + var event; + + if (isFunction(window.CustomEvent)) { + event = new CustomEvent(eventName, { + bubbles: true, + cancelable: true, + detail: data + }); + } else { + event = node.ownerDocument.createEvent('CustomEvent'); + event.initCustomEvent(eventName, true, true, data); + } + + node.dispatchEvent(event); + } + + /** + * Returns if a node is visible. + * + * @param {HTMLElement} + * @returns {boolean} + */ + function isVisible(node) { + return !!node.getClientRects().length; + } + + /** + * Convert CSS property names into camel case + * + * @param {string} string + * @returns {string} + */ + function camelCase(string) { + return string + .replace(/^-ms-/, 'ms-') + .replace(/-(\w)/g, function (match, char) { + return char.toUpperCase(); + }); + } + + + /** + * Loop all child nodes of the passed node + * + * The function should accept 1 parameter being the node. + * If the function returns false the loop will be exited. + * + * @param {HTMLElement} node + * @param {function} func Callback which is called with every + * child node as the first argument. + * @param {boolean} innermostFirst If the innermost node should be passed + * to the function before it's parents. + * @param {boolean} siblingsOnly If to only traverse the nodes siblings + * @param {boolean} [reverse=false] If to traverse the nodes in reverse + */ + // eslint-disable-next-line max-params + function traverse(node, func, innermostFirst, siblingsOnly, reverse) { + node = reverse ? node.lastChild : node.firstChild; + + while (node) { + var next = reverse ? node.previousSibling : node.nextSibling; + + if ( + (!innermostFirst && func(node) === false) || + (!siblingsOnly && traverse( + node, func, innermostFirst, siblingsOnly, reverse + ) === false) || + (innermostFirst && func(node) === false) + ) { + return false; + } + + node = next; + } + } + + /** + * Like traverse but loops in reverse + * @see traverse + */ + function rTraverse(node, func, innermostFirst, siblingsOnly) { + traverse(node, func, innermostFirst, siblingsOnly, true); + } + + /** + * Parses HTML into a document fragment + * + * @param {string} html + * @param {Document} [context] + * @since 1.4.4 + * @return {DocumentFragment} + */ + function parseHTML(html, context) { + context = context || document; + + var ret = context.createDocumentFragment(); + var tmp = createElement('div', {}, context); + + tmp.innerHTML = html; + + while (tmp.firstChild) { + appendChild(ret, tmp.firstChild); + } + + return ret; + } + + /** + * Checks if an element has any styling. + * + * It has styling if it is not a plain
    or

    or + * if it has a class, style attribute or data. + * + * @param {HTMLElement} elm + * @return {boolean} + * @since 1.4.4 + */ + function hasStyling(node) { + return node && (!is(node, 'p,div') || node.className || + attr(node, 'style') || !isEmptyObject(data(node))); + } + + /** + * Converts an element from one type to another. + * + * For example it can convert the element to + * + * @param {HTMLElement} element + * @param {string} toTagName + * @return {HTMLElement} + * @since 1.4.4 + */ + function convertElement(element, toTagName) { + var newElement = createElement(toTagName, {}, element.ownerDocument); + + each(element.attributes, function (_, attribute) { + // Some browsers parse invalid attributes names like + // 'size"2' which throw an exception when set, just + // ignore these. + try { + attr(newElement, attribute.name, attribute.value); + } catch (ex) {} + }); + + while (element.firstChild) { + appendChild(newElement, element.firstChild); + } + + element.parentNode.replaceChild(newElement, element); + + return newElement; + } + + /** + * List of block level elements separated by bars (|) + * + * @type {string} + */ + var blockLevelList = '|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|' + + 'form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|'; + + /** + * List of elements that do not allow children separated by bars (|) + * + * @param {Node} node + * @return {boolean} + * @since 1.4.5 + */ + function canHaveChildren(node) { + // 1 = Element + // 9 = Document + // 11 = Document Fragment + if (!/11?|9/.test(node.nodeType)) { + return false; + } + + // List of empty HTML tags separated by bar (|) character. + // Source: http://www.w3.org/TR/html4/index/elements.html + // Source: http://www.w3.org/TR/html5/syntax.html#void-elements + return ('|iframe|area|base|basefont|br|col|frame|hr|img|input|wbr' + + '|isindex|link|meta|param|command|embed|keygen|source|track|' + + 'object|').indexOf('|' + node.nodeName.toLowerCase() + '|') < 0; + } + + /** + * Checks if an element is inline + * + * @param {HTMLElement} elm + * @param {boolean} [includeCodeAsBlock=false] + * @return {boolean} + */ + function isInline(elm, includeCodeAsBlock) { + var tagName, + nodeType = (elm || {}).nodeType || TEXT_NODE; + + if (nodeType !== ELEMENT_NODE) { + return nodeType === TEXT_NODE; + } + + tagName = elm.tagName.toLowerCase(); + + if (tagName === 'code') { + return !includeCodeAsBlock; + } + + return blockLevelList.indexOf('|' + tagName + '|') < 0; + } + + /** + * Copy the CSS from 1 node to another. + * + * Only copies CSS defined on the element e.g. style attr. + * + * @param {HTMLElement} from + * @param {HTMLElement} to + */ + function copyCSS(from, to) { + to.style.cssText = from.style.cssText + to.style.cssText; + } + + /** + * Fixes block level elements inside in inline elements. + * + * Also fixes invalid list nesting by placing nested lists + * inside the previous li tag or wrapping them in an li tag. + * + * @param {HTMLElement} node + */ + function fixNesting(node) { + var getLastInlineParent = function (node) { + while (isInline(node.parentNode, true)) { + node = node.parentNode; + } + + return node; + }; + + traverse(node, function (node) { + var list = 'ul,ol', + isBlock = !isInline(node, true); + + // Any blocklevel element inside an inline element needs fixing. + if (isBlock && isInline(node.parentNode, true)) { + var parent = getLastInlineParent(node), + before = extractContents(parent, node), + middle = node; + + // copy current styling so when moved out of the parent + // it still has the same styling + copyCSS(parent, middle); + + insertBefore(before, parent); + insertBefore(middle, parent); + } + + // Fix invalid nested lists which should be wrapped in an li tag + if (isBlock && is(node, list) && is(node.parentNode, list)) { + var li = previousElementSibling(node, 'li'); + + if (!li) { + li = createElement('li'); + insertBefore(li, node); + } + + appendChild(li, node); + } + }); + } + + /** + * Finds the common parent of two nodes + * + * @param {!HTMLElement} node1 + * @param {!HTMLElement} node2 + * @return {?HTMLElement} + */ + function findCommonAncestor(node1, node2) { + while ((node1 = node1.parentNode)) { + if (contains(node1, node2)) { + return node1; + } + } + } + + /** + * @param {?Node} + * @param {boolean} [previous=false] + * @returns {?Node} + */ + function getSibling(node, previous) { + if (!node) { + return null; + } + + return (previous ? node.previousSibling : node.nextSibling) || + getSibling(node.parentNode, previous); + } + + /** + * Removes unused whitespace from the root and all it's children. + * + * @param {!HTMLElement} root + * @since 1.4.3 + */ + function removeWhiteSpace(root) { + var nodeValue, nodeType, next, previous, previousSibling, + nextNode, trimStart, + cssWhiteSpace = css(root, 'whiteSpace'), + // Preserve newlines if is pre-line + preserveNewLines = /line$/i.test(cssWhiteSpace), + node = root.firstChild; + + // Skip pre & pre-wrap with any vendor prefix + if (/pre(\-wrap)?$/i.test(cssWhiteSpace)) { + return; + } + + while (node) { + nextNode = node.nextSibling; + nodeValue = node.nodeValue; + nodeType = node.nodeType; + + if (nodeType === ELEMENT_NODE && node.firstChild) { + removeWhiteSpace(node); + } + + if (nodeType === TEXT_NODE) { + next = getSibling(node); + previous = getSibling(node, true); + trimStart = false; + + while (hasClass(previous, 'sceditor-ignore')) { + previous = getSibling(previous, true); + } + + // If previous sibling isn't inline or is a textnode that + // ends in whitespace, time the start whitespace + if (isInline(node) && previous) { + previousSibling = previous; + + while (previousSibling.lastChild) { + previousSibling = previousSibling.lastChild; + + // eslint-disable-next-line max-depth + while (hasClass(previousSibling, 'sceditor-ignore')) { + previousSibling = getSibling(previousSibling, true); + } + } + + trimStart = previousSibling.nodeType === TEXT_NODE ? + /[\t\n\r ]$/.test(previousSibling.nodeValue) : + !isInline(previousSibling); + } + + // Clear zero width spaces + nodeValue = nodeValue.replace(/\u200B/g, ''); + + // Strip leading whitespace + if (!previous || !isInline(previous) || trimStart) { + nodeValue = nodeValue.replace( + preserveNewLines ? /^[\t ]+/ : /^[\t\n\r ]+/, + '' + ); + } + + // Strip trailing whitespace + if (!next || !isInline(next)) { + nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+$/ : /[\t\n\r ]+$/, + '' + ); + } + + // Remove empty text nodes + if (!nodeValue.length) { + remove(node); + } else { + node.nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+/g : /[\t\n\r ]+/g, + ' ' + ); + } + } + + node = nextNode; + } + } + + /** + * Extracts all the nodes between the start and end nodes + * + * @param {HTMLElement} startNode The node to start extracting at + * @param {HTMLElement} endNode The node to stop extracting at + * @return {DocumentFragment} + */ + function extractContents(startNode, endNode) { + var range = startNode.ownerDocument.createRange(); + + range.setStartBefore(startNode); + range.setEndAfter(endNode); + + return range.extractContents(); + } + + /** + * Gets the offset position of an element + * + * @param {HTMLElement} node + * @return {Object} An object with left and top properties + */ + function getOffset(node) { + var left = 0, + top = 0; + + while (node) { + left += node.offsetLeft; + top += node.offsetTop; + node = node.offsetParent; + } + + return { + left: left, + top: top + }; + } + + /** + * Gets the value of a CSS property from the elements style attribute + * + * @param {HTMLElement} elm + * @param {string} property + * @return {string} + */ + function getStyle(elm, property) { + var direction, styleValue, + elmStyle = elm.style; + + if (!cssPropertyNameCache[property]) { + cssPropertyNameCache[property] = camelCase(property); + } + + property = cssPropertyNameCache[property]; + styleValue = elmStyle[property]; + + // Add an exception for text-align + if ('textAlign' === property) { + direction = elmStyle.direction; + styleValue = styleValue || css(elm, property); + + if (css(elm.parentNode, property) === styleValue || + css(elm, 'display') !== 'block' || is(elm, 'hr,th')) { + return ''; + } + + // IE changes text-align to the same as the current direction + // so skip unless its not the same + if ((/right/i.test(styleValue) && direction === 'rtl') || + (/left/i.test(styleValue) && direction === 'ltr')) { + return ''; + } + } + + return styleValue; + } + + /** + * Tests if an element has a style. + * + * If values are specified it will check that the styles value + * matches one of the values + * + * @param {HTMLElement} elm + * @param {string} property + * @param {string|array} [values] + * @return {boolean} + */ + function hasStyle(elm, property, values) { + var styleValue = getStyle(elm, property); + + if (!styleValue) { + return false; + } + + return !values || styleValue === values || + (Array.isArray(values) && values.indexOf(styleValue) > -1); + } + + /** + * Default options for SCEditor + * @type {Object} + */ + var defaultOptions = { + /** @lends jQuery.sceditor.defaultOptions */ + /** + * Toolbar buttons order and groups. Should be comma separated and + * have a bar | to separate groups + * + * @type {string} + */ + toolbar: 'bold,italic,underline,strike,subscript,superscript|' + + 'left,center,right,justify|font,size,color,removeformat|' + + 'cut,copy,pastetext|bulletlist,orderedlist,indent,outdent|' + + 'table|code,quote|horizontalrule,image,email,link,unlink|' + + 'emoticon,youtube,date,time|ltr,rtl|print,maximize,source', + + /** + * Comma separated list of commands to excludes from the toolbar + * + * @type {string} + */ + toolbarExclude: null, + + /** + * Stylesheet to include in the WYSIWYG editor. This is what will style + * the WYSIWYG elements + * + * @type {string} + */ + style: 'jquery.sceditor.default.css', + + /** + * Comma separated list of fonts for the font selector + * + * @type {string} + */ + fonts: 'Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,' + + 'Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana', + + /** + * Colors should be comma separated and have a bar | to signal a new + * column. + * + * If null the colors will be auto generated. + * + * @type {string} + */ + colors: '#000000,#44B8FF,#1E92F7,#0074D9,#005DC2,#00369B,#b3d5f4|' + + '#444444,#C3FFFF,#9DF9FF,#7FDBFF,#68C4E8,#419DC1,#d9f4ff|' + + '#666666,#72FF84,#4CEA5E,#2ECC40,#17B529,#008E02,#c0f0c6|' + + '#888888,#FFFF44,#FFFA1E,#FFDC00,#E8C500,#C19E00,#fff5b3|' + + '#aaaaaa,#FFC95F,#FFA339,#FF851B,#E86E04,#C14700,#ffdbbb|' + + '#cccccc,#FF857A,#FF5F54,#FF4136,#E82A1F,#C10300,#ffc6c3|' + + '#eeeeee,#FF56FF,#FF30DC,#F012BE,#D900A7,#B20080,#fbb8ec|' + + '#ffffff,#F551FF,#CF2BE7,#B10DC9,#9A00B2,#9A00B2,#e8b6ef', + + /** + * The locale to use. + * @type {string} + */ + locale: attr(document.documentElement, 'lang') || 'en', + + /** + * The Charset to use + * @type {string} + */ + charset: 'utf-8', + + /** + * Compatibility mode for emoticons. + * + * Helps if you have emoticons such as :/ which would put an emoticon + * inside http:// + * + * This mode requires emoticons to be surrounded by whitespace or end of + * line chars. This mode has limited As You Type emoticon conversion + * support. It will not replace AYT for end of line chars, only + * emoticons surrounded by whitespace. They will still be replaced + * correctly when loaded just not AYT. + * + * @type {boolean} + */ + emoticonsCompat: false, + + /** + * If to enable emoticons. Can be changes at runtime using the + * emoticons() method. + * + * @type {boolean} + * @since 1.4.2 + */ + emoticonsEnabled: true, + + /** + * Emoticon root URL + * + * @type {string} + */ + emoticonsRoot: '', + emoticons: { + dropdown: { + ':)': 'emoticons/smile.png', + ':angel:': 'emoticons/angel.png', + ':angry:': 'emoticons/angry.png', + '8-)': 'emoticons/cool.png', + ':\'(': 'emoticons/cwy.png', + ':ermm:': 'emoticons/ermm.png', + ':D': 'emoticons/grin.png', + '<3': 'emoticons/heart.png', + ':(': 'emoticons/sad.png', + ':O': 'emoticons/shocked.png', + ':P': 'emoticons/tongue.png', + ';)': 'emoticons/wink.png' + }, + more: { + ':alien:': 'emoticons/alien.png', + ':blink:': 'emoticons/blink.png', + ':blush:': 'emoticons/blush.png', + ':cheerful:': 'emoticons/cheerful.png', + ':devil:': 'emoticons/devil.png', + ':dizzy:': 'emoticons/dizzy.png', + ':getlost:': 'emoticons/getlost.png', + ':happy:': 'emoticons/happy.png', + ':kissing:': 'emoticons/kissing.png', + ':ninja:': 'emoticons/ninja.png', + ':pinch:': 'emoticons/pinch.png', + ':pouty:': 'emoticons/pouty.png', + ':sick:': 'emoticons/sick.png', + ':sideways:': 'emoticons/sideways.png', + ':silly:': 'emoticons/silly.png', + ':sleeping:': 'emoticons/sleeping.png', + ':unsure:': 'emoticons/unsure.png', + ':woot:': 'emoticons/w00t.png', + ':wassat:': 'emoticons/wassat.png' + }, + hidden: { + ':whistling:': 'emoticons/whistling.png', + ':love:': 'emoticons/wub.png' + } + }, + + /** + * Width of the editor. Set to null for automatic with + * + * @type {?number} + */ + width: null, + + /** + * Height of the editor including toolbar. Set to null for automatic + * height + * + * @type {?number} + */ + height: null, + + /** + * If to allow the editor to be resized + * + * @type {boolean} + */ + resizeEnabled: true, + + /** + * Min resize to width, set to null for half textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinWidth: null, + /** + * Min resize to height, set to null for half textarea height or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinHeight: null, + /** + * Max resize to height, set to null for double textarea height or -1 + * for unlimited + * + * @type {?number} + */ + resizeMaxHeight: null, + /** + * Max resize to width, set to null for double textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMaxWidth: null, + /** + * If resizing by height is enabled + * + * @type {boolean} + */ + resizeHeight: true, + /** + * If resizing by width is enabled + * + * @type {boolean} + */ + resizeWidth: true, + + /** + * Date format, will be overridden if locale specifies one. + * + * The words year, month and day will be replaced with the users current + * year, month and day. + * + * @type {string} + */ + dateFormat: 'year-month-day', + + /** + * Element to inset the toolbar into. + * + * @type {HTMLElement} + */ + toolbarContainer: null, + + /** + * If to enable paste filtering. This is currently experimental, please + * report any issues. + * + * @type {boolean} + */ + enablePasteFiltering: false, + + /** + * If to completely disable pasting into the editor + * + * @type {boolean} + */ + disablePasting: false, + + /** + * If the editor is read only. + * + * @type {boolean} + */ + readOnly: false, + + /** + * If to set the editor to right-to-left mode. + * + * If set to null the direction will be automatically detected. + * + * @type {boolean} + */ + rtl: false, + + /** + * If to auto focus the editor on page load + * + * @type {boolean} + */ + autofocus: false, + + /** + * If to auto focus the editor to the end of the content + * + * @type {boolean} + */ + autofocusEnd: true, + + /** + * If to auto expand the editor to fix the content + * + * @type {boolean} + */ + autoExpand: false, + + /** + * If to auto update original textbox on blur + * + * @type {boolean} + */ + autoUpdate: false, + + /** + * If to enable the browsers built in spell checker + * + * @type {boolean} + */ + spellcheck: true, + + /** + * If to run the source editor when there is no WYSIWYG support. Only + * really applies to mobile OS's. + * + * @type {boolean} + */ + runWithoutWysiwygSupport: false, + + /** + * If to load the editor in source mode and still allow switching + * between WYSIWYG and source mode + * + * @type {boolean} + */ + startInSourceMode: false, + + /** + * Optional ID to give the editor. + * + * @type {string} + */ + id: null, + + /** + * Comma separated list of plugins + * + * @type {string} + */ + plugins: '', + + /** + * z-index to set the editor container to. Needed for jQuery UI dialog. + * + * @type {?number} + */ + zIndex: null, + + /** + * If to trim the BBCode. Removes any spaces at the start and end of the + * BBCode string. + * + * @type {boolean} + */ + bbcodeTrim: false, + + /** + * If to disable removing block level elements by pressing backspace at + * the start of them + * + * @type {boolean} + */ + disableBlockRemove: false, + + /** + * BBCode parser options, only applies if using the editor in BBCode + * mode. + * + * See SCEditor.BBCodeParser.defaults for list of valid options + * + * @type {Object} + */ + parserOptions: { }, + + /** + * CSS that will be added to the to dropdown menu (eg. z-index) + * + * @type {Object} + */ + dropDownCss: { } + }; + + var USER_AGENT = navigator.userAgent; + + /** + * Detects the version of IE is being used if any. + * + * Will be the IE version number or undefined if the + * browser is not IE. + * + * Source: https://gist.github.com/527683 with extra code + * for IE 10 & 11 detection. + * + * @function + * @name ie + * @type {number} + */ + var ie = (function () { + var undef, + v = 3, + doc = document, + div = doc.createElement('div'), + all = div.getElementsByTagName('i'); + + do { + div.innerHTML = ''; + } while (all[0]); + + // Detect IE 10 as it doesn't support conditional comments. + if ((doc.documentMode && doc.all && window.atob)) { + v = 10; + } + + // Detect IE 11 + if (v === 4 && doc.documentMode) { + v = 11; + } + + return v > 4 ? v : undef; + }()); + + var edge = '-ms-ime-align' in document.documentElement.style; + + /** + * Detects if the browser is iOS + * + * Needed to fix iOS specific bugs + * + * @function + * @name ios + * @memberOf jQuery.sceditor + * @type {boolean} + */ + var ios = /iPhone|iPod|iPad| wosbrowser\//i.test(USER_AGENT); + + /** + * If the browser supports WYSIWYG editing (e.g. older mobile browsers). + * + * @function + * @name isWysiwygSupported + * @return {boolean} + */ + var isWysiwygSupported = (function () { + var match, isUnsupported; + + var div = document.createElement('div'); + div.contentEditable = true ; + + // Check if the contentEditable attribute is supported + if (!('contentEditable' in document.documentElement) || + div.contentEditable !== 'true') { + return false; + } + + // I think blackberry supports contentEditable or will at least + // give a valid value for the contentEditable detection above + // so it isn't included in the below tests. + + // I hate having to do UA sniffing but some mobile browsers say they + // support contentediable when it isn't usable, i.e. you can't enter + // text. + // This is the only way I can think of to detect them which is also how + // every other editor I've seen deals with this issue. + + // Exclude Opera mobile and mini + isUnsupported = /Opera Mobi|Opera Mini/i.test(USER_AGENT); + + if (/Android/i.test(USER_AGENT)) { + isUnsupported = true; + + if (/Safari/.test(USER_AGENT)) { + // Android browser 534+ supports content editable + // This also matches Chrome which supports content editable too + match = /Safari\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + } + + // The current version of Amazon Silk supports it, older versions didn't + // As it uses webkit like Android, assume it's the same and started + // working at versions >= 534 + if (/ Silk\//i.test(USER_AGENT)) { + match = /AppleWebKit\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + + // iOS 5+ supports content editable + if (ios) { + // Block any version <= 4_x(_x) + isUnsupported = /OS [0-4](_\d)+ like Mac/i.test(USER_AGENT); + } + + // Firefox does support WYSIWYG on mobiles so override + // any previous value if using FF + if (/Firefox/i.test(USER_AGENT)) { + isUnsupported = false; + } + + if (/OneBrowser/i.test(USER_AGENT)) { + isUnsupported = false; + } + + // UCBrowser works but doesn't give a unique user agent + if (navigator.vendor === 'UCWEB') { + isUnsupported = false; + } + + // IE <= 9 is not supported any more + if (ie <= 9) { + isUnsupported = true; + } + + return !isUnsupported; + }()); + + // Must start with a valid scheme + // ^ + // Schemes that are considered safe + // (https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):| + // Relative schemes (//:) are considered safe + // (\\/\\/)| + // Image data URI's are considered safe + // data:image\\/(png|bmp|gif|p?jpe?g); + var VALID_SCHEME_REGEX = + /^(https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):|(\/\/)|data:image\/(png|bmp|gif|p?jpe?g);/i; + + /** + * Escapes a string so it's safe to use in regex + * + * @param {string} str + * @return {string} + */ + function regex(str) { + return str.replace(/([\-.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + } + + /** + * Escapes all HTML entities in a string + * + * If noQuotes is set to false, all single and double + * quotes will also be escaped + * + * @param {string} str + * @param {boolean} [noQuotes=true] + * @return {string} + * @since 1.4.1 + */ + function entities(str, noQuotes) { + if (!str) { + return str; + } + + var replacements = { + '&': '&', + '<': '<', + '>': '>', + ' ': '  ', + '\r\n': '
    ', + '\r': '
    ', + '\n': '
    ' + }; + + if (noQuotes !== false) { + replacements['"'] = '"'; + replacements['\''] = '''; + replacements['`'] = '`'; + } + + str = str.replace(/ {2}|\r\n|[&<>\r\n'"`]/g, function (match) { + return replacements[match] || match; + }); + + return str; + } + + /** + * Escape URI scheme. + * + * Appends the current URL to a url if it has a scheme that is not: + * + * http + * https + * sftp + * ftp + * mailto + * spotify + * skype + * ssh + * teamspeak + * tel + * // + * data:image/(png|jpeg|jpg|pjpeg|bmp|gif); + * + * **IMPORTANT**: This does not escape any HTML in a url, for + * that use the escape.entities() method. + * + * @param {string} url + * @return {string} + * @since 1.4.5 + */ + function uriScheme(url) { + var path, + // If there is a : before a / then it has a scheme + hasScheme = /^[^\/]*:/i, + location = window.location; + + // Has no scheme or a valid scheme + if ((!url || !hasScheme.test(url)) || VALID_SCHEME_REGEX.test(url)) { + return url; + } + + path = location.pathname.split('/'); + path.pop(); + + return location.protocol + '//' + + location.host + + path.join('/') + '/' + + url; + } + + /** + * HTML templates used by the editor and default commands + * @type {Object} + * @private + */ + var _templates = { + html: + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

    ' + + '', + + toolbarButton: '' + + '
    {dispName}
    ', + + emoticon: '', + + fontOpt: '{font}', + + sizeOpt: '{size}', + + pastetext: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + table: + '
    ' + + '
    ' + + '
    ', + + image: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + email: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + link: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtubeMenu: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtube: + '' + }; + + /** + * Replaces any params in a template with the passed params. + * + * If createHtml is passed it will return a DocumentFragment + * containing the parsed template. + * + * @param {string} name + * @param {Object} [params] + * @param {boolean} [createHtml] + * @returns {string|DocumentFragment} + * @private + */ + function _tmpl (name, params, createHtml) { + var template = _templates[name]; + + Object.keys(params).forEach(function (name) { + template = template.replace( + new RegExp(regex('{' + name + '}'), 'g'), params[name] + ); + }); + + if (createHtml) { + template = parseHTML(template); + } + + return template; + } + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX = ie && ie < 11; + + /** + * Fixes a bug in FF where it sometimes wraps + * new lines in their own list item. + * See issue #359 + */ + function fixFirefoxListBug(editor) { + // Only apply to Firefox as will break other browsers. + if ('mozHidden' in document) { + var node = editor.getBody(); + var next; + + while (node) { + next = node; + + if (next.firstChild) { + next = next.firstChild; + } else { + + while (next && !next.nextSibling) { + next = next.parentNode; + } + + if (next) { + next = next.nextSibling; + } + } + + if (node.nodeType === 3 && /[\n\r\t]+/.test(node.nodeValue)) { + // Only remove if newlines are collapsed + if (!/^pre/.test(css(node.parentNode, 'whiteSpace'))) { + remove(node); + } + } + + node = next; + } + } + } + + + /** + * Map of all the commands for SCEditor + * @type {Object} + * @name commands + * @memberOf jQuery.sceditor + */ + var defaultCmds = { + // START_COMMAND: Bold + bold: { + exec: 'bold', + tooltip: 'Bold', + shortcut: 'Ctrl+B' + }, + // END_COMMAND + // START_COMMAND: Italic + italic: { + exec: 'italic', + tooltip: 'Italic', + shortcut: 'Ctrl+I' + }, + // END_COMMAND + // START_COMMAND: Underline + underline: { + exec: 'underline', + tooltip: 'Underline', + shortcut: 'Ctrl+U' + }, + // END_COMMAND + // START_COMMAND: Strikethrough + strike: { + exec: 'strikethrough', + tooltip: 'Strikethrough' + }, + // END_COMMAND + // START_COMMAND: Subscript + subscript: { + exec: 'subscript', + tooltip: 'Subscript' + }, + // END_COMMAND + // START_COMMAND: Superscript + superscript: { + exec: 'superscript', + tooltip: 'Superscript' + }, + // END_COMMAND + + // START_COMMAND: Left + left: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'left' || align === (isLtr ? 'start' : 'end'); + } + }, + exec: 'justifyleft', + tooltip: 'Align left' + }, + // END_COMMAND + // START_COMMAND: Centre + center: { + exec: 'justifycenter', + tooltip: 'Center' + }, + // END_COMMAND + // START_COMMAND: Right + right: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'right' || align === (isLtr ? 'end' : 'start'); + } + }, + exec: 'justifyright', + tooltip: 'Align right' + }, + // END_COMMAND + // START_COMMAND: Justify + justify: { + exec: 'justifyfull', + tooltip: 'Justify' + }, + // END_COMMAND + + // START_COMMAND: Font + font: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'font')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.opts.fonts.split(',').forEach(function (font) { + appendChild(content, _tmpl('fontOpt', { + font: font + }, true)); + }); + + editor.createDropDown(caller, 'font-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.font._dropDown(editor, caller, function (fontName) { + editor.execCommand('fontname', fontName); + }); + }, + tooltip: 'Font Name' + }, + // END_COMMAND + // START_COMMAND: Size + size: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'size')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + for (var i = 1; i <= 7; i++) { + appendChild(content, _tmpl('sizeOpt', { + size: i + }, true)); + } + + editor.createDropDown(caller, 'fontsize-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.size._dropDown(editor, caller, function (fontSize) { + editor.execCommand('fontsize', fontSize); + }); + }, + tooltip: 'Font Size' + }, + // END_COMMAND + // START_COMMAND: Colour + color: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'), + html = '', + cmd = defaultCmds.color; + + if (!cmd._htmlCache) { + editor.opts.colors.split('|').forEach(function (column) { + html += '
    '; + + column.split(',').forEach(function (color) { + html += + ''; + }); + + html += '
    '; + }); + + cmd._htmlCache = html; + } + + appendChild(content, parseHTML(cmd._htmlCache)); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'color')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'color-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.color._dropDown(editor, caller, function (color) { + editor.execCommand('forecolor', color); + }); + }, + tooltip: 'Font Color' + }, + // END_COMMAND + // START_COMMAND: Remove Format + removeformat: { + exec: 'removeformat', + tooltip: 'Remove Formatting' + }, + // END_COMMAND + + // START_COMMAND: Cut + cut: { + exec: 'cut', + tooltip: 'Cut', + errorMessage: 'Your browser does not allow the cut command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-X' + }, + // END_COMMAND + // START_COMMAND: Copy + copy: { + exec: 'copy', + tooltip: 'Copy', + errorMessage: 'Your browser does not allow the copy command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-C' + }, + // END_COMMAND + // START_COMMAND: Paste + paste: { + exec: 'paste', + tooltip: 'Paste', + errorMessage: 'Your browser does not allow the paste command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-V' + }, + // END_COMMAND + // START_COMMAND: Paste Text + pastetext: { + exec: function (caller) { + var val, + content = createElement('div'), + editor = this; + + appendChild(content, _tmpl('pastetext', { + label: editor._( + 'Paste your text inside the following box:' + ), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + val = find(content, '#txt')[0].value; + + if (val) { + editor.wysiwygEditorInsertText(val); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'pastetext', content); + }, + tooltip: 'Paste Text' + }, + // END_COMMAND + // START_COMMAND: Bullet List + bulletlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertunorderedlist'); + }, + tooltip: 'Bullet list' + }, + // END_COMMAND + // START_COMMAND: Ordered List + orderedlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertorderedlist'); + }, + tooltip: 'Numbered list' + }, + // END_COMMAND + // START_COMMAND: Indent + indent: { + state: function (parent$$1, firstBlock) { + // Only works with lists, for now + var range, startParent, endParent; + + if (is(firstBlock, 'li')) { + return 0; + } + + if (is(firstBlock, 'ul,ol,menu')) { + // if the whole list is selected, then this must be + // invalidated because the browser will place a + //
    there + range = this.getRangeHelper().selectedRange(); + + startParent = range.startContainer.parentNode; + endParent = range.endContainer.parentNode; + + // TODO: could use nodeType for this? + // Maybe just check the firstBlock contains both the start + //and end containers + + // Select the tag, not the textNode + // (that's why the parentNode) + if (startParent !== + startParent.parentNode.firstElementChild || + // work around a bug in FF + (is(endParent, 'li') && endParent !== + endParent.parentNode.lastElementChild)) { + return 0; + } + } + + return -1; + }, + exec: function () { + var editor = this, + block = editor.getRangeHelper().getFirstBlockParent(); + + editor.focus(); + + // An indent system is quite complicated as there are loads + // of complications and issues around how to indent text + // As default, let's just stay with indenting the lists, + // at least, for now. + if (closest(block, 'ul,ol,menu')) { + editor.execCommand('indent'); + } + }, + tooltip: 'Add indent' + }, + // END_COMMAND + // START_COMMAND: Outdent + outdent: { + state: function (parents$$1, firstBlock) { + return closest(firstBlock, 'ul,ol,menu') ? 0 : -1; + }, + exec: function () { + var block = this.getRangeHelper().getFirstBlockParent(); + if (closest(block, 'ul,ol,menu')) { + this.execCommand('outdent'); + } + }, + tooltip: 'Remove one indent' + }, + // END_COMMAND + + // START_COMMAND: Table + table: { + exec: function (caller) { + var editor = this, + content = createElement('div'); + + appendChild(content, _tmpl('table', { + rows: editor._('Rows:'), + cols: editor._('Cols:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var rows = Number(find(content, '#rows')[0].value), + cols = Number(find(content, '#cols')[0].value), + html = ''; + + if (rows > 0 && cols > 0) { + html += Array(rows + 1).join( + '' + + Array(cols + 1).join( + '' + ) + + '' + ); + + html += '
    ' + (IE_BR_FIX ? '' : '
    ') + '
    '; + + editor.wysiwygEditorInsertHtml(html); + editor.closeDropDown(true); + e.preventDefault(); + } + }); + + editor.createDropDown(caller, 'inserttable', content); + }, + tooltip: 'Insert a table' + }, + // END_COMMAND + + // START_COMMAND: Horizontal Rule + horizontalrule: { + exec: 'inserthorizontalrule', + tooltip: 'Insert a horizontal rule' + }, + // END_COMMAND + + // START_COMMAND: Code + code: { + exec: function () { + this.wysiwygEditorInsertHtml( + '', + (IE_BR_FIX ? '' : '
    ') + '
    ' + ); + }, + tooltip: 'Code' + }, + // END_COMMAND + + // START_COMMAND: Image + image: { + _dropDown: function (editor, caller, selected, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('image', { + url: editor._('URL:'), + width: editor._('Width (optional):'), + height: editor._('Height (optional):'), + insert: editor._('Insert') + }, true)); + + + var urlInput = find(content, '#image')[0]; + + urlInput.value = selected; + + on(content, 'click', '.button', function (e) { + if (urlInput.value) { + cb( + urlInput.value, + find(content, '#width')[0].value, + find(content, '#height')[0].value + ); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertimage', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.image._dropDown( + editor, + caller, + '', + function (url, width$$1, height$$1) { + var attrs = ''; + + if (width$$1) { + attrs += ' width="' + width$$1 + '"'; + } + + if (height$$1) { + attrs += ' height="' + height$$1 + '"'; + } + + editor.wysiwygEditorInsertHtml( + '' + ); + } + ); + }, + tooltip: 'Insert an image' + }, + // END_COMMAND + + // START_COMMAND: E-mail + email: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('email', { + label: editor._('E-mail:'), + desc: editor._('Description (optional):'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var email = find(content, '#email')[0].value; + + if (email) { + cb(email, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertemail', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.email._dropDown( + editor, + caller, + function (email, text) { + // needed for IE to reset the last range + editor.focus(); + + if (!editor.getRangeHelper().selectedHtml() || text) { + editor.wysiwygEditorInsertHtml( + '' + + (text || email) + + '' + ); + } else { + editor.execCommand('createlink', 'mailto:' + email); + } + } + ); + }, + tooltip: 'Insert an email' + }, + // END_COMMAND + + // START_COMMAND: Link + link: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('link', { + url: editor._('URL:'), + desc: editor._('Description (optional):'), + ins: editor._('Insert') + }, true)); + + var linkInput = find(content, '#link')[0]; + + function insertUrl(e) { + if (linkInput.value) { + cb(linkInput.value, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + } + + on(content, 'click', '.button', insertUrl); + on(content, 'keypress', function (e) { + // 13 = enter key + if (e.which === 13 && linkInput.value) { + insertUrl(e); + } + }, EVENT_CAPTURE); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.link._dropDown(editor, caller, function (url, text) { + // needed for IE to restore the last range + editor.focus(); + + // If there is no selected text then must set the URL as + // the text. Most browsers do this automatically, sadly + // IE doesn't. + if (text || !editor.getRangeHelper().selectedHtml()) { + text = text || url; + + editor.wysiwygEditorInsertHtml( + '' + text + '' + ); + } else { + editor.execCommand('createlink', url); + } + }); + }, + tooltip: 'Insert a link' + }, + // END_COMMAND + + // START_COMMAND: Unlink + unlink: { + state: function () { + return closest(this.currentNode(), 'a') ? 0 : -1; + }, + exec: function () { + var anchor = closest(this.currentNode(), 'a'); + + if (anchor) { + while (anchor.firstChild) { + insertBefore(anchor.firstChild, anchor); + } + + remove(anchor); + } + }, + tooltip: 'Unlink' + }, + // END_COMMAND + + + // START_COMMAND: Quote + quote: { + exec: function (caller, html, author) { + var before = '
    ', + end = '
    '; + + // if there is HTML passed set end to null so any selected + // text is replaced + if (html) { + author = (author ? '' + author + '' : ''); + before = before + author + html + end; + end = null; + // if not add a newline to the end of the inserted quote + } else if (this.getRangeHelper().selectedHtml() === '') { + end = (IE_BR_FIX ? '' : '
    ') + end; + } + + this.wysiwygEditorInsertHtml(before, end); + }, + tooltip: 'Insert a Quote' + }, + // END_COMMAND + + // START_COMMAND: Emoticons + emoticon: { + exec: function (caller) { + var editor = this; + + var createContent = function (includeMore) { + var moreLink, + opts = editor.opts, + emoticonsRoot = opts.emoticonsRoot || '', + emoticonsCompat = opts.emoticonsCompat, + rangeHelper = editor.getRangeHelper(), + startSpace = emoticonsCompat && + rangeHelper.getOuterText(true, 1) !== ' ' ? ' ' : '', + endSpace = emoticonsCompat && + rangeHelper.getOuterText(false, 1) !== ' ' ? ' ' : '', + content = createElement('div'), + line = createElement('div'), + perLine = 0, + emoticons = extend( + {}, + opts.emoticons.dropdown, + includeMore ? opts.emoticons.more : {} + ); + + appendChild(content, line); + + perLine = Math.sqrt(Object.keys(emoticons).length); + + on(content, 'click', 'img', function (e) { + editor.insert(startSpace + attr(this, 'alt') + endSpace, + null, false).closeDropDown(true); + + e.preventDefault(); + }); + + each(emoticons, function (code, emoticon) { + appendChild(line, createElement('img', { + src: emoticonsRoot + (emoticon.url || emoticon), + alt: code, + title: emoticon.tooltip || code + })); + + if (line.children.length >= perLine) { + line = createElement('div'); + appendChild(content, line); + } + }); + + if (!includeMore && opts.emoticons.more) { + moreLink = createElement('a', { + className: 'sceditor-more' + }); + + appendChild(moreLink, + document.createTextNode(editor._('More'))); + + on(moreLink, 'click', function (e) { + editor.createDropDown( + caller, 'more-emoticons', createContent(true) + ); + + e.preventDefault(); + }); + + appendChild(content, moreLink); + } + + return content; + }; + + editor.createDropDown(caller, 'emoticons', createContent(false)); + }, + txtExec: function (caller) { + defaultCmds.emoticon.exec.call(this, caller); + }, + tooltip: 'Insert an emoticon' + }, + // END_COMMAND + + // START_COMMAND: YouTube + youtube: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + appendChild(content, _tmpl('youtubeMenu', { + label: editor._('Video URL:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var val = find(content, '#link')[0].value; + var idMatch = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/); + var timeMatch = val.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/); + var time = 0; + + if (timeMatch) { + each(timeMatch[1].split(/[hms]/), function (i, val) { + if (val !== '') { + time = (time * 60) + Number(val); + } + }); + } + + if (idMatch && /^[a-zA-Z0-9_\-]{11}$/.test(idMatch[1])) { + callback(idMatch[1], time); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (btn) { + var editor = this; + + defaultCmds.youtube._dropDown(editor, btn, function (id, time) { + editor.wysiwygEditorInsertHtml(_tmpl('youtube', { + id: id, + time: time + })); + }); + }, + tooltip: 'Insert a YouTube video' + }, + // END_COMMAND + + // START_COMMAND: Date + date: { + _date: function (editor) { + var now = new Date(), + year = now.getYear(), + month = now.getMonth() + 1, + day = now.getDate(); + + if (year < 2000) { + year = 1900 + year; + } + + if (month < 10) { + month = '0' + month; + } + + if (day < 10) { + day = '0' + day; + } + + return editor.opts.dateFormat + .replace(/year/i, year) + .replace(/month/i, month) + .replace(/day/i, day); + }, + exec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + txtExec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + tooltip: 'Insert current date' + }, + // END_COMMAND + + // START_COMMAND: Time + time: { + _time: function () { + var now = new Date(), + hours = now.getHours(), + mins = now.getMinutes(), + secs = now.getSeconds(); + + if (hours < 10) { + hours = '0' + hours; + } + + if (mins < 10) { + mins = '0' + mins; + } + + if (secs < 10) { + secs = '0' + secs; + } + + return hours + ':' + mins + ':' + secs; + }, + exec: function () { + this.insertText(defaultCmds.time._time()); + }, + txtExec: function () { + this.insertText(defaultCmds.time._time()); + }, + tooltip: 'Insert current time' + }, + // END_COMMAND + + + // START_COMMAND: Ltr + ltr: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'ltr'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'ltr' ? '' : 'ltr'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Left-to-Right' + }, + // END_COMMAND + + // START_COMMAND: Rtl + rtl: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'rtl'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'rtl' ? '' : 'rtl'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Right-to-Left' + }, + // END_COMMAND + + + // START_COMMAND: Print + print: { + exec: 'print', + tooltip: 'Print' + }, + // END_COMMAND + + // START_COMMAND: Maximize + maximize: { + state: function () { + return this.maximize(); + }, + exec: function () { + this.maximize(!this.maximize()); + }, + txtExec: function () { + this.maximize(!this.maximize()); + }, + tooltip: 'Maximize', + shortcut: 'Ctrl+Shift+M' + }, + // END_COMMAND + + // START_COMMAND: Source + source: { + state: function () { + return this.sourceMode(); + }, + exec: function () { + this.toggleSourceMode(); + }, + txtExec: function () { + this.toggleSourceMode(); + }, + tooltip: 'View source', + shortcut: 'Ctrl+Shift+S' + }, + // END_COMMAND + + // this is here so that commands above can be removed + // without having to remove the , after the last one. + // Needed for IE. + ignore: {} + }; + + var plugins = {}; + + /** + * Plugin Manager class + * @class PluginManager + * @name PluginManager + */ + function PluginManager(thisObj) { + /** + * Alias of this + * + * @private + * @type {Object} + */ + var base = this; + + /** + * Array of all currently registered plugins + * + * @type {Array} + * @private + */ + var registeredPlugins = []; + + + /** + * Changes a signals name from "name" into "signalName". + * + * @param {string} signal + * @return {string} + * @private + */ + var formatSignalName = function (signal) { + return 'signal' + signal.charAt(0).toUpperCase() + signal.slice(1); + }; + + /** + * Calls handlers for a signal + * + * @see call() + * @see callOnlyFirst() + * @param {Array} args + * @param {boolean} returnAtFirst + * @return {*} + * @private + */ + var callHandlers = function (args, returnAtFirst) { + args = [].slice.call(args); + + var idx, ret, + signal = formatSignalName(args.shift()); + + for (idx = 0; idx < registeredPlugins.length; idx++) { + if (signal in registeredPlugins[idx]) { + ret = registeredPlugins[idx][signal].apply(thisObj, args); + + if (returnAtFirst) { + return ret; + } + } + } + }; + + /** + * Calls all handlers for the passed signal + * + * @param {string} signal + * @param {...string} args + * @function + * @name call + * @memberOf PluginManager.prototype + */ + base.call = function () { + callHandlers(arguments, false); + }; + + /** + * Calls the first handler for a signal, and returns the + * + * @param {string} signal + * @param {...string} args + * @return {*} The result of calling the handler + * @function + * @name callOnlyFirst + * @memberOf PluginManager.prototype + */ + base.callOnlyFirst = function () { + return callHandlers(arguments, true); + }; + + /** + * Checks if a signal has a handler + * + * @param {string} signal + * @return {boolean} + * @function + * @name hasHandler + * @memberOf PluginManager.prototype + */ + base.hasHandler = function (signal) { + var i = registeredPlugins.length; + signal = formatSignalName(signal); + + while (i--) { + if (signal in registeredPlugins[i]) { + return true; + } + } + + return false; + }; + + /** + * Checks if the plugin exists in plugins + * + * @param {string} plugin + * @return {boolean} + * @function + * @name exists + * @memberOf PluginManager.prototype + */ + base.exists = function (plugin) { + if (plugin in plugins) { + plugin = plugins[plugin]; + + return typeof plugin === 'function' && + typeof plugin.prototype === 'object'; + } + + return false; + }; + + /** + * Checks if the passed plugin is currently registered. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name isRegistered + * @memberOf PluginManager.prototype + */ + base.isRegistered = function (plugin) { + if (base.exists(plugin)) { + var idx = registeredPlugins.length; + + while (idx--) { + if (registeredPlugins[idx] instanceof plugins[plugin]) { + return true; + } + } + } + + return false; + }; + + /** + * Registers a plugin to receive signals + * + * @param {string} plugin + * @return {boolean} + * @function + * @name register + * @memberOf PluginManager.prototype + */ + base.register = function (plugin) { + if (!base.exists(plugin) || base.isRegistered(plugin)) { + return false; + } + + plugin = new plugins[plugin](); + registeredPlugins.push(plugin); + + if ('init' in plugin) { + plugin.init.call(thisObj); + } + + return true; + }; + + /** + * Deregisters a plugin. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name deregister + * @memberOf PluginManager.prototype + */ + base.deregister = function (plugin) { + var removedPlugin, + pluginIdx = registeredPlugins.length, + removed = false; + + if (!base.isRegistered(plugin)) { + return removed; + } + + while (pluginIdx--) { + if (registeredPlugins[pluginIdx] instanceof plugins[plugin]) { + removedPlugin = registeredPlugins.splice(pluginIdx, 1)[0]; + removed = true; + + if ('destroy' in removedPlugin) { + removedPlugin.destroy.call(thisObj); + } + } + } + + return removed; + }; + + /** + * Clears all plugins and removes the owner reference. + * + * Calling any functions on this object after calling + * destroy will cause a JS error. + * + * @name destroy + * @memberOf PluginManager.prototype + */ + base.destroy = function () { + var i = registeredPlugins.length; + + while (i--) { + if ('destroy' in registeredPlugins[i]) { + registeredPlugins[i].destroy.call(thisObj); + } + } + + registeredPlugins = []; + thisObj = null; + }; + } + + PluginManager.plugins = plugins; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$1 = ie && ie < 11; + + + /** + * Gets the text, start/end node and offset for + * length chars left or right of the passed node + * at the specified offset. + * + * @param {Node} node + * @param {number} offset + * @param {boolean} isLeft + * @param {number} length + * @return {Object} + * @private + */ + var outerText = function (range, isLeft, length) { + var nodeValue, remaining, start, end, node, + text = '', + next = range.startContainer, + offset = range.startOffset; + + // Handle cases where node is a paragraph and offset + // refers to the index of a text node. + // 3 = text node + if (next && next.nodeType !== 3) { + next = next.childNodes[offset]; + offset = 0; + } + + start = end = offset; + + while (length > text.length && next && next.nodeType === 3) { + nodeValue = next.nodeValue; + remaining = length - text.length; + + // If not the first node, start and end should be at their + // max values as will be updated when getting the text + if (node) { + end = nodeValue.length; + start = 0; + } + + node = next; + + if (isLeft) { + start = Math.max(end - remaining, 0); + offset = start; + + text = nodeValue.substr(start, end - start) + text; + next = node.previousSibling; + } else { + end = Math.min(remaining, nodeValue.length); + offset = start + end; + + text += nodeValue.substr(start, end); + next = node.nextSibling; + } + } + + return { + node: node || next, + offset: offset, + text: text + }; + }; + + /** + * Range helper + * + * @class RangeHelper + * @name RangeHelper + */ + function RangeHelper(win, d) { + var _createMarker, _prepareInput, + doc = d || win.contentDocument || win.document, + startMarker = 'sceditor-start-marker', + endMarker = 'sceditor-end-marker', + base = this; + + /** + * Inserts HTML into the current range replacing any selected + * text. + * + * If endHTML is specified the selected contents will be put between + * html and endHTML. If there is nothing selected html and endHTML are + * just concatenate together. + * + * @param {string} html + * @param {string} [endHTML] + * @return False on fail + * @function + * @name insertHTML + * @memberOf RangeHelper.prototype + */ + base.insertHTML = function (html, endHTML) { + var node, div, + range = base.selectedRange(); + + if (!range) { + return false; + } + + if (endHTML) { + html += base.selectedHtml() + endHTML; + } + + div = createElement('p', {}, doc); + node = doc.createDocumentFragment(); + div.innerHTML = html; + + while (div.firstChild) { + appendChild(node, div.firstChild); + } + + base.insertNode(node); + }; + + /** + * Prepares HTML to be inserted by adding a zero width space + * if the last child is empty and adding the range start/end + * markers to the last child. + * + * @param {Node|string} node + * @param {Node|string} [endNode] + * @param {boolean} [returnHtml] + * @return {Node|string} + * @private + */ + _prepareInput = function (node, endNode, returnHtml) { + var lastChild, + frag = doc.createDocumentFragment(); + + if (typeof node === 'string') { + if (endNode) { + node += base.selectedHtml() + endNode; + } + + frag = parseHTML(node); + } else { + appendChild(frag, node); + + if (endNode) { + appendChild(frag, base.selectedRange().extractContents()); + appendChild(frag, endNode); + } + } + + if (!(lastChild = frag.lastChild)) { + return; + } + + while (!isInline(lastChild.lastChild, true)) { + lastChild = lastChild.lastChild; + } + + if (canHaveChildren(lastChild)) { + // Webkit won't allow the cursor to be placed inside an + // empty tag, so add a zero width space to it. + if (!lastChild.lastChild) { + appendChild(lastChild, document.createTextNode('\u200B')); + } + } else { + lastChild = frag; + } + + base.removeMarkers(); + + // Append marks to last child so when restored cursor will be in + // the right place + appendChild(lastChild, _createMarker(startMarker)); + appendChild(lastChild, _createMarker(endMarker)); + + if (returnHtml) { + var div = createElement('div'); + appendChild(div, frag); + + return div.innerHTML; + } + + return frag; + }; + + /** + * The same as insertHTML except with DOM nodes instead + * + * Warning: the nodes must belong to the + * document they are being inserted into. Some browsers + * will throw exceptions if they don't. + * + * Returns boolean false on fail + * + * @param {Node} node + * @param {Node} endNode + * @return {false|undefined} + * @function + * @name insertNode + * @memberOf RangeHelper.prototype + */ + base.insertNode = function (node, endNode) { + var input = _prepareInput(node, endNode), + range = base.selectedRange(), + parent$$1 = range.commonAncestorContainer; + + if (!input) { + return false; + } + + range.deleteContents(); + + // FF allows
    to be selected but inserting a node + // into
    will cause it not to be displayed so must + // insert before the
    in FF. + // 3 = TextNode + if (parent$$1 && parent$$1.nodeType !== 3 && !canHaveChildren(parent$$1)) { + insertBefore(input, parent$$1); + } else { + range.insertNode(input); + } + + base.restoreRange(); + }; + + /** + * Clones the selected Range + * + * @return {Range} + * @function + * @name cloneSelected + * @memberOf RangeHelper.prototype + */ + base.cloneSelected = function () { + var range = base.selectedRange(); + + if (range) { + return range.cloneRange(); + } + }; + + /** + * Gets the selected Range + * + * @return {Range} + * @function + * @name selectedRange + * @memberOf RangeHelper.prototype + */ + base.selectedRange = function () { + var range, firstChild, + sel = win.getSelection(); + + if (!sel) { + return; + } + + // When creating a new range, set the start to the first child + // element of the body element to avoid errors in FF. + if (sel.rangeCount <= 0) { + firstChild = doc.body; + while (firstChild.firstChild) { + firstChild = firstChild.firstChild; + } + + range = doc.createRange(); + // Must be setStartBefore otherwise it can cause infinite + // loops with lists in WebKit. See issue 442 + range.setStartBefore(firstChild); + + sel.addRange(range); + } + + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } + + return range; + }; + + /** + * Gets if there is currently a selection + * + * @return {boolean} + * @function + * @name hasSelection + * @since 1.4.4 + * @memberOf RangeHelper.prototype + */ + base.hasSelection = function () { + var sel = win.getSelection(); + + return sel && sel.rangeCount > 0; + }; + + /** + * Gets the currently selected HTML + * + * @return {string} + * @function + * @name selectedHtml + * @memberOf RangeHelper.prototype + */ + base.selectedHtml = function () { + var div, + range = base.selectedRange(); + + if (range) { + div = createElement('p', {}, doc); + appendChild(div, range.cloneContents()); + + return div.innerHTML; + } + + return ''; + }; + + /** + * Gets the parent node of the selected contents in the range + * + * @return {HTMLElement} + * @function + * @name parentNode + * @memberOf RangeHelper.prototype + */ + base.parentNode = function () { + var range = base.selectedRange(); + + if (range) { + return range.commonAncestorContainer; + } + }; + + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @return {HTMLElement} + * @function + * @name getFirstBlockParent + * @memberOf RangeHelper.prototype + */ + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @param {Node} [n] The element to get the first block level parent from + * @return {HTMLElement} + * @function + * @name getFirstBlockParent^2 + * @since 1.4.1 + * @memberOf RangeHelper.prototype + */ + base.getFirstBlockParent = function (node) { + var func = function (elm) { + if (!isInline(elm, true)) { + return elm; + } + + elm = elm ? elm.parentNode : null; + + return elm ? func(elm) : elm; + }; + + return func(node || base.parentNode()); + }; + + /** + * Inserts a node at either the start or end of the current selection + * + * @param {Bool} start + * @param {Node} node + * @function + * @name insertNodeAt + * @memberOf RangeHelper.prototype + */ + base.insertNodeAt = function (start, node) { + var currentRange = base.selectedRange(), + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(start); + range.insertNode(node); + + // Reselect the current range. + // Fixes issue with Chrome losing the selection. Issue#82 + base.selectRange(currentRange); + }; + + /** + * Creates a marker node + * + * @param {string} id + * @return {HTMLSpanElement} + * @private + */ + _createMarker = function (id) { + base.removeMarker(id); + + var marker = createElement('span', { + id: id, + className: 'sceditor-selection sceditor-ignore', + style: 'display:none;line-height:0' + }, doc); + + marker.innerHTML = ' '; + + return marker; + }; + + /** + * Inserts start/end markers for the current selection + * which can be used by restoreRange to re-select the + * range. + * + * @memberOf RangeHelper.prototype + * @function + * @name insertMarkers + */ + base.insertMarkers = function () { + var currentRange = base.selectedRange(); + var startNode = _createMarker(startMarker); + + base.removeMarkers(); + base.insertNodeAt(true, startNode); + + // Fixes issue with end marker sometimes being placed before + // the start marker when the range is collapsed. + if (currentRange && currentRange.collapsed) { + startNode.parentNode.insertBefore( + _createMarker(endMarker), startNode.nextSibling); + } else { + base.insertNodeAt(false, _createMarker(endMarker)); + } + }; + + /** + * Gets the marker with the specified ID + * + * @param {string} id + * @return {Node} + * @function + * @name getMarker + * @memberOf RangeHelper.prototype + */ + base.getMarker = function (id) { + return doc.getElementById(id); + }; + + /** + * Removes the marker with the specified ID + * + * @param {string} id + * @function + * @name removeMarker + * @memberOf RangeHelper.prototype + */ + base.removeMarker = function (id) { + var marker = base.getMarker(id); + + if (marker) { + remove(marker); + } + }; + + /** + * Removes the start/end markers + * + * @function + * @name removeMarkers + * @memberOf RangeHelper.prototype + */ + base.removeMarkers = function () { + base.removeMarker(startMarker); + base.removeMarker(endMarker); + }; + + /** + * Saves the current range location. Alias of insertMarkers() + * + * @function + * @name saveRage + * @memberOf RangeHelper.prototype + */ + base.saveRange = function () { + base.insertMarkers(); + }; + + /** + * Select the specified range + * + * @param {Range} range + * @function + * @name selectRange + * @memberOf RangeHelper.prototype + */ + base.selectRange = function (range) { + var lastChild; + var sel = win.getSelection(); + var container = range.endContainer; + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$1 && range.collapsed && container && + !isInline(container, true)) { + + lastChild = container.lastChild; + while (lastChild && is(lastChild, '.sceditor-ignore')) { + lastChild = lastChild.previousSibling; + } + + if (is(lastChild, 'br')) { + var rng = doc.createRange(); + rng.setEndAfter(lastChild); + rng.collapse(false); + + if (base.compare(range, rng)) { + range.setStartBefore(lastChild); + range.collapse(true); + } + } + } + + if (sel) { + base.clear(); + sel.addRange(range); + } + }; + + /** + * Restores the last range saved by saveRange() or insertMarkers() + * + * @function + * @name restoreRange + * @memberOf RangeHelper.prototype + */ + base.restoreRange = function () { + var isCollapsed, + range = base.selectedRange(), + start = base.getMarker(startMarker), + end = base.getMarker(endMarker); + + if (!start || !end || !range) { + return false; + } + + isCollapsed = start.nextSibling === end; + + range = doc.createRange(); + range.setStartBefore(start); + range.setEndAfter(end); + + if (isCollapsed) { + range.collapse(true); + } + + base.selectRange(range); + base.removeMarkers(); + }; + + /** + * Selects the text left and right of the current selection + * + * @param {number} left + * @param {number} right + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.selectOuterText = function (left, right) { + var start, end, + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(false); + + start = outerText(range, true, left); + end = outerText(range, false, right); + + range.setStart(start.node, start.offset); + range.setEnd(end.node, end.offset); + + base.selectRange(range); + }; + + /** + * Gets the text left or right of the current selection + * + * @param {boolean} before + * @param {number} length + * @return {string} + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.getOuterText = function (before, length) { + var range = base.cloneSelected(); + + if (!range) { + return ''; + } + + range.collapse(!before); + + return outerText(range, before, length).text; + }; + + /** + * Replaces keywords with values based on the current caret position + * + * @param {Array} keywords + * @param {boolean} includeAfter If to include the text after the + * current caret position or just + * text before + * @param {boolean} keywordsSorted If the keywords array is pre + * sorted shortest to longest + * @param {number} longestKeyword Length of the longest keyword + * @param {boolean} requireWhitespace If the key must be surrounded + * by whitespace + * @param {string} keypressChar If this is being called from + * a keypress event, this should be + * set to the pressed character + * @return {boolean} + * @function + * @name replaceKeyword + * @memberOf RangeHelper.prototype + */ + // eslint-disable-next-line max-params + base.replaceKeyword = function ( + keywords, + includeAfter, + keywordsSorted, + longestKeyword, + requireWhitespace, + keypressChar + ) { + if (!keywordsSorted) { + keywords.sort(function (a, b) { + return a[0].length - b[0].length; + }); + } + + var outerText, match, matchPos, startIndex, + leftLen, charsLeft, keyword, keywordLen, + whitespaceRegex = '(^|[\\s\xA0\u2002\u2003\u2009])', + keywordIdx = keywords.length, + whitespaceLen = requireWhitespace ? 1 : 0, + maxKeyLen = longestKeyword || + keywords[keywordIdx - 1][0].length; + + if (requireWhitespace) { + maxKeyLen++; + } + + keypressChar = keypressChar || ''; + outerText = base.getOuterText(true, maxKeyLen); + leftLen = outerText.length; + outerText += keypressChar; + + if (includeAfter) { + outerText += base.getOuterText(false, maxKeyLen); + } + + while (keywordIdx--) { + keyword = keywords[keywordIdx][0]; + keywordLen = keyword.length; + startIndex = Math.max(0, leftLen - keywordLen - whitespaceLen); + matchPos = -1; + + if (requireWhitespace) { + match = outerText + .substr(startIndex) + .match(new RegExp(whitespaceRegex + + regex(keyword) + whitespaceRegex)); + + if (match) { + // Add the length of the text that was removed by + // substr() and also add 1 for the whitespace + matchPos = match.index + startIndex + match[1].length; + } + } else { + matchPos = outerText.indexOf(keyword, startIndex); + } + + if (matchPos > -1) { + // Make sure the match is between before and + // after, not just entirely in one side or the other + if (matchPos <= leftLen && + matchPos + keywordLen + whitespaceLen >= leftLen) { + charsLeft = leftLen - matchPos; + + // If the keypress char is white space then it should + // not be replaced, only chars that are part of the + // key should be replaced. + base.selectOuterText( + charsLeft, + keywordLen - charsLeft - + (/^\S/.test(keypressChar) ? 1 : 0) + ); + + base.insertHTML(keywords[keywordIdx][1]); + return true; + } + } + } + + return false; + }; + + /** + * Compares two ranges. + * + * If rangeB is undefined it will be set to + * the current selected range + * + * @param {Range} rngA + * @param {Range} [rngB] + * @return {boolean} + * @function + * @name compare + * @memberOf RangeHelper.prototype + */ + base.compare = function (rngA, rngB) { + if (!rngB) { + rngB = base.selectedRange(); + } + + if (!rngA || !rngB) { + return !rngA && !rngB; + } + + return rngA.compareBoundaryPoints(Range.END_TO_END, rngB) === 0 && + rngA.compareBoundaryPoints(Range.START_TO_START, rngB) === 0; + }; + + /** + * Removes any current selection + * + * @since 1.4.6 + * @function + * @name clear + * @memberOf RangeHelper.prototype + */ + base.clear = function () { + var sel = win.getSelection(); + + if (sel) { + if (sel.removeAllRanges) { + sel.removeAllRanges(); + } else if (sel.empty) { + sel.empty(); + } + } + }; + } + + /** + * Checks all emoticons are surrounded by whitespace and + * replaces any that aren't with with their emoticon code. + * + * @param {HTMLElement} node + * @param {rangeHelper} rangeHelper + * @return {void} + */ + function checkWhitespace(node, rangeHelper) { + var noneWsRegex = /[^\s\xA0\u2002\u2003\u2009\u00a0]+/; + var emoticons = node && find(node, 'img[data-sceditor-emoticon]'); + + if (!node || !emoticons.length) { + return; + } + + for (var i = 0; i < emoticons.length; i++) { + var emoticon = emoticons[i]; + var parent$$1 = emoticon.parentNode; + var prev = emoticon.previousSibling; + var next = emoticon.nextSibling; + + if ((!prev || !noneWsRegex.test(prev.nodeValue.slice(-1))) && + (!next || !noneWsRegex.test((next.nodeValue || '')[0]))) { + continue; + } + + var range = rangeHelper.cloneSelected(); + var rangeStart = -1; + var rangeStartContainer = range.startContainer; + var previousText = prev.nodeValue; + + // For IE's HTMLPhraseElement + if (previousText === null) { + previousText = prev.innerText || ''; + } + + previousText += data(emoticon, 'sceditor-emoticon'); + + // If the cursor is after the removed emoticon, add + // the length of the newly added text to it + if (rangeStartContainer === next) { + rangeStart = previousText.length + range.startOffset; + } + + // If the cursor is set before the next node, set it to + // the end of the new text node + if (rangeStartContainer === node && + node.childNodes[range.startOffset] === next) { + rangeStart = previousText.length; + } + + // If the cursor is set before the removed emoticon, + // just keep it at that position + if (rangeStartContainer === prev) { + rangeStart = range.startOffset; + } + + if (!next || next.nodeType !== TEXT_NODE) { + next = parent$$1.insertBefore( + parent$$1.ownerDocument.createTextNode(''), next + ); + } + + next.insertData(0, previousText); + remove(prev); + remove(emoticon); + + // Need to update the range starting position if it's been modified + if (rangeStart > -1) { + range.setStart(next, rangeStart); + range.collapse(true); + rangeHelper.selectRange(range); + } + } + } + + /** + * Replaces any emoticons inside the root node with images. + * + * emoticons should be an object where the key is the emoticon + * code and the value is the HTML to replace it with. + * + * @param {HTMLElement} root + * @param {Object} emoticons + * @param {boolean} emoticonsCompat + * @return {void} + */ + function replace(root, emoticons, emoticonsCompat) { + var doc = root.ownerDocument; + var space = '(^|\\s|\xA0|\u2002|\u2003|\u2009|$)'; + var emoticonCodes = []; + var emoticonRegex = {}; + + // TODO: Make this tag configurable. + if (parent(root, 'code')) { + return; + } + + each(emoticons, function (key) { + emoticonRegex[key] = new RegExp(space + regex(key) + space); + emoticonCodes.push(key); + }); + + // Sort keys longest to shortest so that longer keys + // take precedence (avoids bugs with shorter keys partially + // matching longer ones) + emoticonCodes.sort(function (a, b) { + return b.length - a.length; + }); + + (function convert(node) { + node = node.firstChild; + + while (node) { + // TODO: Make this tag configurable. + if (node.nodeType === ELEMENT_NODE && !is(node, 'code')) { + convert(node); + } + + if (node.nodeType === TEXT_NODE) { + for (var i = 0; i < emoticonCodes.length; i++) { + var text = node.nodeValue; + var key = emoticonCodes[i]; + var index = emoticonsCompat ? + text.search(emoticonRegex[key]) : + text.indexOf(key); + + if (index > -1) { + // When emoticonsCompat is enabled this will be the + // position after any white space + var startIndex = text.indexOf(key, index); + var fragment = parseHTML(emoticons[key], doc); + var after = text.substr(startIndex + key.length); + + fragment.appendChild(doc.createTextNode(after)); + + node.nodeValue = text.substr(0, startIndex); + node.parentNode + .insertBefore(fragment, node.nextSibling); + } + } + } + + node = node.nextSibling; + } + }(root)); + } + + var globalWin = window; + var globalDoc = document; + + var IE_VER = ie; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$2 = IE_VER && IE_VER < 11; + + var IMAGE_MIME_REGEX = /^image\/(p?jpe?g|gif|png|bmp)$/i; + + /** + * Wrap inlines that are in the root in paragraphs. + * + * @param {HTMLBodyElement} body + * @param {Document} doc + * @private + */ + function wrapInlines(body, doc) { + var wrapper; + + traverse(body, function (node) { + if (isInline(node, true)) { + if (!wrapper) { + wrapper = createElement('p', {}, doc); + insertBefore(wrapper, node); + } + + if (node.nodeType !== TEXT_NODE || node.nodeValue !== '') { + appendChild(wrapper, node); + } + } else { + wrapper = null; + } + }, false, true); + } + + /** + * SCEditor - A lightweight WYSIWYG editor + * + * @param {HTMLTextAreaElement} original The textarea to be converted + * @param {Object} userOptions + * @class SCEditor + * @name SCEditor + */ + function SCEditor(original, userOptions) { + /** + * Alias of this + * + * @private + */ + var base = this; + + /** + * Editor format like BBCode or HTML + */ + var format; + + /** + * The div which contains the editor and toolbar + * + * @type {HTMLDivElement} + * @private + */ + var editorContainer; + + /** + * Map of events handlers bound to this instance. + * + * @type {Object} + * @private + */ + var eventHandlers = {}; + + /** + * The editors toolbar + * + * @type {HTMLDivElement} + * @private + */ + var toolbar; + + /** + * The editors iframe which should be in design mode + * + * @type {HTMLIFrameElement} + * @private + */ + var wysiwygEditor; + + /** + * The editors window + * + * @type {Window} + * @private + */ + var wysiwygWindow; + + /** + * The WYSIWYG editors body element + * + * @type {HTMLBodyElement} + * @private + */ + var wysiwygBody; + + /** + * The WYSIWYG editors document + * + * @type {Document} + * @private + */ + var wysiwygDocument; + + /** + * The editors textarea for viewing source + * + * @type {HTMLTextAreaElement} + * @private + */ + var sourceEditor; + + /** + * The current dropdown + * + * @type {HTMLDivElement} + * @private + */ + var dropdown; + + /** + * Store the last cursor position. Needed for IE because it forgets + * + * @type {Range} + * @private + */ + var lastRange; + + /** + * If the user is currently composing text via IME + * @type {boolean} + */ + var isComposing; + + /** + * Timer for valueChanged key handler + * @type {number} + */ + var valueChangedKeyUpTimer; + + /** + * The editors locale + * + * @private + */ + var locale; + + /** + * Stores a cache of preloaded images + * + * @private + * @type {Array.} + */ + var preLoadCache = []; + + /** + * The editors rangeHelper instance + * + * @type {RangeHelper} + * @private + */ + var rangeHelper; + + /** + * An array of button state handlers + * + * @type {Array.} + * @private + */ + var btnStateHandlers = []; + + /** + * Plugin manager instance + * + * @type {PluginManager} + * @private + */ + var pluginManager; + + /** + * The current node containing the selection/caret + * + * @type {Node} + * @private + */ + var currentNode; + + /** + * The first block level parent of the current node + * + * @type {node} + * @private + */ + var currentBlockNode; + + /** + * The current node selection/caret + * + * @type {Object} + * @private + */ + var currentSelection; + + /** + * Used to make sure only 1 selection changed + * check is called every 100ms. + * + * Helps improve performance as it is checked a lot. + * + * @type {boolean} + * @private + */ + var isSelectionCheckPending; + + /** + * If content is required (equivalent to the HTML5 required attribute) + * + * @type {boolean} + * @private + */ + var isRequired; + + /** + * The inline CSS style element. Will be undefined + * until css() is called for the first time. + * + * @type {HTMLStyleElement} + * @private + */ + var inlineCss; + + /** + * Object containing a list of shortcut handlers + * + * @type {Object} + * @private + */ + var shortcutHandlers = {}; + + /** + * The min and max heights that autoExpand should stay within + * + * @type {Object} + * @private + */ + var autoExpandBounds; + + /** + * Timeout for the autoExpand function to throttle calls + * + * @private + */ + var autoExpandThrottle; + + /** + * Cache of the current toolbar buttons + * + * @type {Object} + * @private + */ + var toolbarButtons = {}; + + /** + * Last scroll position before maximizing so + * it can be restored when finished. + * + * @type {number} + * @private + */ + var maximizeScrollPosition; + + /** + * Stores the contents while a paste is taking place. + * + * Needed to support browsers that lack clipboard API support. + * + * @type {?DocumentFragment} + * @private + */ + var pasteContentFragment; + + /** + * All the emoticons from dropdown, more and hidden combined + * and with the emoticons root set + * + * @type {!Object} + * @private + */ + var allEmoticons = {}; + + /** + * Current icon set if any + * + * @type {?Object} + * @private + */ + var icons; + + /** + * Private functions + * @private + */ + var init, + replaceEmoticons, + handleCommand, + saveRange, + initEditor, + initPlugins, + initLocale, + initToolBar, + initOptions, + initEvents, + initResize, + initEmoticons, + handlePasteEvt, + handlePasteData, + handleKeyDown, + handleBackSpace, + handleKeyPress, + handleFormReset, + handleMouseDown, + handleComposition, + handleEvent, + handleDocumentClick, + updateToolBar, + updateActiveButtons, + sourceEditorSelectedText, + appendNewLine, + checkSelectionChanged, + checkNodeChanged, + autofocus, + emoticonsKeyPress, + emoticonsCheckWhitespace, + currentStyledBlockNode, + triggerValueChanged, + valueChangedBlur, + valueChangedKeyUp, + autoUpdate, + autoExpand; + + /** + * All the commands supported by the editor + * @name commands + * @memberOf SCEditor.prototype + */ + base.commands = extend(true, {}, (userOptions.commands || defaultCmds)); + + /** + * Options for this editor instance + * @name opts + * @memberOf SCEditor.prototype + */ + var options = base.opts = extend( + true, {}, defaultOptions, userOptions + ); + + // Don't deep extend emoticons (fixes #565) + base.opts.emoticons = userOptions.emoticons || defaultOptions.emoticons; + + /** + * Creates the editor iframe and textarea + * @private + */ + init = function () { + original._sceditor = base; + + // Load locale + if (options.locale && options.locale !== 'en') { + initLocale(); + } + + editorContainer = createElement('div', { + className: 'sceditor-container' + }); + + insertBefore(editorContainer, original); + css(editorContainer, 'z-index', options.zIndex); + + // Add IE version to the container to allow IE specific CSS + // fixes without using CSS hacks or conditional comments + if (IE_VER) { + addClass(editorContainer, 'ie ie' + IE_VER); + } + + isRequired = original.required; + original.required = false; + + var FormatCtor = SCEditor.formats[options.format]; + format = FormatCtor ? new FormatCtor() : {}; + if ('init' in format) { + format.init.call(base); + } + + // create the editor + initPlugins(); + initEmoticons(); + initToolBar(); + initEditor(); + initOptions(); + initEvents(); + + // force into source mode if is a browser that can't handle + // full editing + if (!isWysiwygSupported) { + base.toggleSourceMode(); + } + + updateActiveButtons(); + + var loaded = function () { + off(globalWin, 'load', loaded); + + if (options.autofocus) { + autofocus(); + } + + autoExpand(); + appendNewLine(); + // TODO: use editor doc and window? + pluginManager.call('ready'); + if ('onReady' in format) { + format.onReady.call(base); + } + }; + on(globalWin, 'load', loaded); + if (globalDoc.readyState === 'complete') { + loaded(); + } + }; + + initPlugins = function () { + var plugins = options.plugins; + + plugins = plugins ? plugins.toString().split(',') : []; + pluginManager = new PluginManager(base); + + plugins.forEach(function (plugin) { + pluginManager.register(plugin.trim()); + }); + }; + + /** + * Init the locale variable with the specified locale if possible + * @private + * @return void + */ + initLocale = function () { + var lang; + + locale = SCEditor.locale[options.locale]; + + if (!locale) { + lang = options.locale.split('-'); + locale = SCEditor.locale[lang[0]]; + } + + // Locale DateTime format overrides any specified in the options + if (locale && locale.dateFormat) { + options.dateFormat = locale.dateFormat; + } + }; + + /** + * Creates the editor iframe and textarea + * @private + */ + initEditor = function () { + sourceEditor = createElement('textarea'); + wysiwygEditor = createElement('iframe', { + frameborder: 0, + allowfullscreen: true + }); + + /* This needs to be done right after they are created because, + * for any reason, the user may not want the value to be tinkered + * by any filters. + */ + if (options.startInSourceMode) { + addClass(editorContainer, 'sourceMode'); + hide(wysiwygEditor); + } else { + addClass(editorContainer, 'wysiwygMode'); + hide(sourceEditor); + } + + if (!options.spellcheck) { + attr(editorContainer, 'spellcheck', 'false'); + } + + if (globalWin.location.protocol === 'https:') { + // eslint-disable-next-line no-script-url + attr(wysiwygEditor, 'src', 'javascript:false'); + } + + // Add the editor to the container + appendChild(editorContainer, wysiwygEditor); + appendChild(editorContainer, sourceEditor); + + // TODO: make this optional somehow + base.dimensions( + options.width || width(original), + options.height || height(original) + ); + + // Add IE version class to the HTML element so can apply + // conditional styling without CSS hacks + var className = IE_VER ? 'ie ie' + IE_VER : ''; + // Add ios to HTML so can apply CSS fix to only it + className += ios ? ' ios' : ''; + + wysiwygDocument = wysiwygEditor.contentDocument; + wysiwygDocument.open(); + wysiwygDocument.write(_tmpl('html', { + attrs: ' class="' + className + '"', + spellcheck: options.spellcheck ? '' : 'spellcheck="false"', + charset: options.charset, + style: options.style + })); + wysiwygDocument.close(); + + wysiwygBody = wysiwygDocument.body; + wysiwygWindow = wysiwygEditor.contentWindow; + + base.readOnly(!!options.readOnly); + + // iframe overflow fix for iOS, also fixes an IE issue with the + // editor not getting focus when clicking inside + if (ios || edge || IE_VER) { + height(wysiwygBody, '100%'); + + if (!IE_VER) { + on(wysiwygBody, 'touchend', base.focus); + } + } + + var tabIndex = attr(original, 'tabindex'); + attr(sourceEditor, 'tabindex', tabIndex); + attr(wysiwygEditor, 'tabindex', tabIndex); + + rangeHelper = new RangeHelper(wysiwygWindow); + + // load any textarea value into the editor + hide(original); + base.val(original.value); + + var placeholder = options.placeholder || + attr(original, 'placeholder'); + + if (placeholder) { + sourceEditor.placeholder = placeholder; + attr(wysiwygBody, 'placeholder', placeholder); + } + }; + + /** + * Initialises options + * @private + */ + initOptions = function () { + // auto-update original textbox on blur if option set to true + if (options.autoUpdate) { + on(wysiwygBody, 'blur', autoUpdate); + on(sourceEditor, 'blur', autoUpdate); + } + + if (options.rtl === null) { + options.rtl = css(sourceEditor, 'direction') === 'rtl'; + } + + base.rtl(!!options.rtl); + + if (options.autoExpand) { + // Need to update when images (or anything else) loads + on(wysiwygBody, 'load', autoExpand, EVENT_CAPTURE); + on(wysiwygBody, 'input keyup', autoExpand); + } + + if (options.resizeEnabled) { + initResize(); + } + + attr(editorContainer, 'id', options.id); + base.emoticons(options.emoticonsEnabled); + }; + + /** + * Initialises events + * @private + */ + initEvents = function () { + var form = original.form; + var compositionEvents = 'compositionstart compositionend'; + var eventsToForward = 'keydown keyup keypress focus blur contextmenu'; + var checkSelectionEvents = 'onselectionchange' in wysiwygDocument ? + 'selectionchange' : + 'keyup focus blur contextmenu mouseup touchend click'; + + on(globalDoc, 'click', handleDocumentClick); + + if (form) { + on(form, 'reset', handleFormReset); + on(form, 'submit', base.updateOriginal, EVENT_CAPTURE); + } + + on(wysiwygBody, 'keypress', handleKeyPress); + on(wysiwygBody, 'keydown', handleKeyDown); + on(wysiwygBody, 'keydown', handleBackSpace); + on(wysiwygBody, 'keyup', appendNewLine); + on(wysiwygBody, 'blur', valueChangedBlur); + on(wysiwygBody, 'keyup', valueChangedKeyUp); + on(wysiwygBody, 'paste', handlePasteEvt); + on(wysiwygBody, compositionEvents, handleComposition); + on(wysiwygBody, checkSelectionEvents, checkSelectionChanged); + on(wysiwygBody, eventsToForward, handleEvent); + + if (options.emoticonsCompat && globalWin.getSelection) { + on(wysiwygBody, 'keyup', emoticonsCheckWhitespace); + } + + on(wysiwygBody, 'blur', function () { + if (!base.val()) { + addClass(wysiwygBody, 'placeholder'); + } + }); + + on(wysiwygBody, 'focus', function () { + removeClass(wysiwygBody, 'placeholder'); + }); + + on(sourceEditor, 'blur', valueChangedBlur); + on(sourceEditor, 'keyup', valueChangedKeyUp); + on(sourceEditor, 'keydown', handleKeyDown); + on(sourceEditor, compositionEvents, handleComposition); + on(sourceEditor, eventsToForward, handleEvent); + + on(wysiwygDocument, 'mousedown', handleMouseDown); + on(wysiwygDocument, checkSelectionEvents, checkSelectionChanged); + on(wysiwygDocument, 'beforedeactivate keyup mouseup', saveRange); + on(wysiwygDocument, 'keyup', appendNewLine); + on(wysiwygDocument, 'focus', function () { + lastRange = null; + }); + + on(editorContainer, 'selectionchanged', checkNodeChanged); + on(editorContainer, 'selectionchanged', updateActiveButtons); + // Custom events to forward + on( + editorContainer, + 'selectionchanged valuechanged nodechanged pasteraw paste', + handleEvent + ); + }; + + /** + * Creates the toolbar and appends it to the container + * @private + */ + initToolBar = function () { + var group, + commands = base.commands, + exclude = (options.toolbarExclude || '').split(','), + groups = options.toolbar.split('|'); + + toolbar = createElement('div', { + className: 'sceditor-toolbar', + unselectable: 'on' + }); + + if (options.icons in SCEditor.icons) { + icons = new SCEditor.icons[options.icons](); + } + + each(groups, function (_, menuItems) { + group = createElement('div', { + className: 'sceditor-group' + }); + + each(menuItems.split(','), function (_, commandName) { + var button, shortcut, + command = commands[commandName]; + + // The commandName must be a valid command and not excluded + if (!command || exclude.indexOf(commandName) > -1) { + return; + } + + shortcut = command.shortcut; + button = _tmpl('toolbarButton', { + name: commandName, + dispName: base._(command.name || + command.tooltip || commandName) + }, true).firstChild; + + if (icons && icons.create) { + var icon = icons.create(commandName); + if (icon) { + insertBefore(icons.create(commandName), + button.firstChild); + addClass(button, 'has-icon'); + } + } + + button._sceTxtMode = !!command.txtExec; + button._sceWysiwygMode = !!command.exec; + toggleClass(button, 'disabled', !command.exec); + on(button, 'click', function (e) { + if (!hasClass(button, 'disabled')) { + handleCommand(button, command); + } + + updateActiveButtons(); + e.preventDefault(); + }); + // Prevent editor losing focus when button clicked + on(button, 'mousedown', function (e) { + base.closeDropDown(); + e.preventDefault(); + }); + + if (command.tooltip) { + attr(button, 'title', + base._(command.tooltip) + + (shortcut ? ' (' + shortcut + ')' : '') + ); + } + + if (shortcut) { + base.addShortcut(shortcut, commandName); + } + + if (command.state) { + btnStateHandlers.push({ + name: commandName, + state: command.state + }); + // exec string commands can be passed to queryCommandState + } else if (isString(command.exec)) { + btnStateHandlers.push({ + name: commandName, + state: command.exec + }); + } + + appendChild(group, button); + toolbarButtons[commandName] = button; + }); + + // Exclude empty groups + if (group.firstChild) { + appendChild(toolbar, group); + } + }); + + // Append the toolbar to the toolbarContainer option if given + appendChild(options.toolbarContainer || editorContainer, toolbar); + }; + + /** + * Creates the resizer. + * @private + */ + initResize = function () { + var minHeight, maxHeight, minWidth, maxWidth, + mouseMoveFunc, mouseUpFunc, + grip = createElement('div', { + className: 'sceditor-grip' + }), + // Cover is used to cover the editor iframe so document + // still gets mouse move events + cover = createElement('div', { + className: 'sceditor-resize-cover' + }), + moveEvents = 'touchmove mousemove', + endEvents = 'touchcancel touchend mouseup', + startX = 0, + startY = 0, + newX = 0, + newY = 0, + startWidth = 0, + startHeight = 0, + origWidth = width(editorContainer), + origHeight = height(editorContainer), + isDragging = false, + rtl = base.rtl(); + + minHeight = options.resizeMinHeight || origHeight / 1.5; + maxHeight = options.resizeMaxHeight || origHeight * 2.5; + minWidth = options.resizeMinWidth || origWidth / 1.25; + maxWidth = options.resizeMaxWidth || origWidth * 1.25; + + mouseMoveFunc = function (e) { + // iOS uses window.event + if (e.type === 'touchmove') { + e = globalWin.event; + newX = e.changedTouches[0].pageX; + newY = e.changedTouches[0].pageY; + } else { + newX = e.pageX; + newY = e.pageY; + } + + var newHeight = startHeight + (newY - startY), + newWidth = rtl ? + startWidth - (newX - startX) : + startWidth + (newX - startX); + + if (maxWidth > 0 && newWidth > maxWidth) { + newWidth = maxWidth; + } + if (minWidth > 0 && newWidth < minWidth) { + newWidth = minWidth; + } + if (!options.resizeWidth) { + newWidth = false; + } + + if (maxHeight > 0 && newHeight > maxHeight) { + newHeight = maxHeight; + } + if (minHeight > 0 && newHeight < minHeight) { + newHeight = minHeight; + } + if (!options.resizeHeight) { + newHeight = false; + } + + if (newWidth || newHeight) { + base.dimensions(newWidth, newHeight); + } + + e.preventDefault(); + }; + + mouseUpFunc = function (e) { + if (!isDragging) { + return; + } + + isDragging = false; + + hide(cover); + removeClass(editorContainer, 'resizing'); + off(globalDoc, moveEvents, mouseMoveFunc); + off(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }; + + if (icons && icons.create) { + var icon = icons.create('grip'); + if (icon) { + appendChild(grip, icon); + addClass(grip, 'has-icon'); + } + } + + appendChild(editorContainer, grip); + appendChild(editorContainer, cover); + hide(cover); + + on(grip, 'touchstart mousedown', function (e) { + // iOS uses window.event + if (e.type === 'touchstart') { + e = globalWin.event; + startX = e.touches[0].pageX; + startY = e.touches[0].pageY; + } else { + startX = e.pageX; + startY = e.pageY; + } + + startWidth = width(editorContainer); + startHeight = height(editorContainer); + isDragging = true; + + addClass(editorContainer, 'resizing'); + show(cover); + on(globalDoc, moveEvents, mouseMoveFunc); + on(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }); + }; + + /** + * Prefixes and preloads the emoticon images + * @private + */ + initEmoticons = function () { + var emoticons = options.emoticons; + var root = options.emoticonsRoot || ''; + + if (emoticons) { + allEmoticons = extend( + {}, emoticons.more, emoticons.dropdown, emoticons.hidden + ); + } + + each(allEmoticons, function (key, url) { + allEmoticons[key] = _tmpl('emoticon', { + key: key, + // Prefix emoticon root to emoticon urls + url: root + (url.url || url), + tooltip: url.tooltip || key + }); + + // Preload the emoticon + if (options.emoticonsEnabled) { + preLoadCache.push(createElement('img', { + src: root + (url.url || url) + })); + } + }); + }; + + /** + * Autofocus the editor + * @private + */ + autofocus = function () { + var range, txtPos, + node = wysiwygBody.firstChild, + focusEnd = !!options.autofocusEnd; + + // Can't focus invisible elements + if (!isVisible(editorContainer)) { + return; + } + + if (base.sourceMode()) { + txtPos = focusEnd ? sourceEditor.value.length : 0; + + sourceEditor.setSelectionRange(txtPos, txtPos); + + return; + } + + removeWhiteSpace(wysiwygBody); + + if (focusEnd) { + if (!(node = wysiwygBody.lastChild)) { + node = createElement('p', {}, wysiwygDocument); + appendChild(wysiwygBody, node); + } + + while (node.lastChild) { + node = node.lastChild; + + // IE < 11 should place the cursor after the
    as + // it will show it as a newline. IE >= 11 and all + // other browsers should place the cursor before. + if (!IE_BR_FIX$2 && is(node, 'br') && node.previousSibling) { + node = node.previousSibling; + } + } + } + + range = wysiwygDocument.createRange(); + + if (!canHaveChildren(node)) { + range.setStartBefore(node); + + if (focusEnd) { + range.setStartAfter(node); + } + } else { + range.selectNodeContents(node); + } + + range.collapse(!focusEnd); + rangeHelper.selectRange(range); + currentSelection = range; + + if (focusEnd) { + wysiwygBody.scrollTop = wysiwygBody.scrollHeight; + } + + base.focus(); + }; + + /** + * Gets if the editor is read only + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly + * @return {boolean} + */ + /** + * Sets if the editor is read only + * + * @param {boolean} readOnly + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly^2 + * @return {this} + */ + base.readOnly = function (readOnly) { + if (typeof readOnly !== 'boolean') { + return !sourceEditor.readonly; + } + + wysiwygBody.contentEditable = !readOnly; + sourceEditor.readonly = !readOnly; + + updateToolBar(readOnly); + + return base; + }; + + /** + * Gets if the editor is in RTL mode + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl + * @return {boolean} + */ + /** + * Sets if the editor is in RTL mode + * + * @param {boolean} rtl + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl^2 + * @return {this} + */ + base.rtl = function (rtl) { + var dir = rtl ? 'rtl' : 'ltr'; + + if (typeof rtl !== 'boolean') { + return attr(sourceEditor, 'dir') === 'rtl'; + } + + attr(wysiwygBody, 'dir', dir); + attr(sourceEditor, 'dir', dir); + + removeClass(editorContainer, 'rtl'); + removeClass(editorContainer, 'ltr'); + addClass(editorContainer, dir); + + if (icons && icons.rtl) { + icons.rtl(rtl); + } + + return base; + }; + + /** + * Updates the toolbar to disable/enable the appropriate buttons + * @private + */ + updateToolBar = function (disable) { + var mode = base.inSourceMode() ? '_sceTxtMode' : '_sceWysiwygMode'; + + each(toolbarButtons, function (_, button) { + toggleClass(button, 'disabled', disable || !button[mode]); + }); + }; + + /** + * Gets the width of the editor in pixels + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width + * @return {number} + */ + /** + * Sets the width of the editor + * + * @param {number} width Width in pixels + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width^2 + * @return {this} + */ + /** + * Sets the width of the editor + * + * The saveWidth specifies if to save the width. The stored width can be + * used for things like restoring from maximized state. + * + * @param {number} width Width in pixels + * @param {boolean} [saveWidth=true] If to store the width + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name width^3 + * @return {this} + */ + base.width = function (width$$1, saveWidth) { + if (!width$$1 && width$$1 !== 0) { + return width(editorContainer); + } + + base.dimensions(width$$1, null, saveWidth); + + return base; + }; + + /** + * Returns an object with the properties width and height + * which are the width and height of the editor in px. + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions + * @return {object} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^2 + * @return {this} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + *

    The save argument specifies if to save the new sizes. + * The saved sizes can be used for things like restoring from + * maximized state. This should normally be left as true.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @param {boolean} [save=true] If to store the new sizes + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^3 + * @return {this} + */ + base.dimensions = function (width$$1, height$$1, save) { + // set undefined width/height to boolean false + width$$1 = (!width$$1 && width$$1 !== 0) ? false : width$$1; + height$$1 = (!height$$1 && height$$1 !== 0) ? false : height$$1; + + if (width$$1 === false && height$$1 === false) { + return { width: base.width(), height: base.height() }; + } + + if (width$$1 !== false) { + if (save !== false) { + options.width = width$$1; + } + + width(editorContainer, width$$1); + } + + if (height$$1 !== false) { + if (save !== false) { + options.height = height$$1; + } + + height(editorContainer, height$$1); + } + + return base; + }; + + /** + * Gets the height of the editor in px + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height + * @return {number} + */ + /** + * Sets the height of the editor + * + * @param {number} height Height in px + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height^2 + * @return {this} + */ + /** + * Sets the height of the editor + * + * The saveHeight specifies if to save the height. + * + * The stored height can be used for things like + * restoring from maximized state. + * + * @param {number} height Height in px + * @param {boolean} [saveHeight=true] If to store the height + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name height^3 + * @return {this} + */ + base.height = function (height$$1, saveHeight) { + if (!height$$1 && height$$1 !== 0) { + return height(editorContainer); + } + + base.dimensions(null, height$$1, saveHeight); + + return base; + }; + + /** + * Gets if the editor is maximised or not + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize + * @return {boolean} + */ + /** + * Sets if the editor is maximised or not + * + * @param {boolean} maximize If to maximise the editor + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize^2 + * @return {this} + */ + base.maximize = function (maximize) { + var maximizeSize = 'sceditor-maximize'; + + if (isUndefined(maximize)) { + return hasClass(editorContainer, maximizeSize); + } + + maximize = !!maximize; + + if (maximize) { + maximizeScrollPosition = globalWin.pageYOffset; + } + + toggleClass(globalDoc.documentElement, maximizeSize, maximize); + toggleClass(globalDoc.body, maximizeSize, maximize); + toggleClass(editorContainer, maximizeSize, maximize); + base.width(maximize ? '100%' : options.width, false); + base.height(maximize ? '100%' : options.height, false); + + if (!maximize) { + globalWin.scrollTo(0, maximizeScrollPosition); + } + + autoExpand(); + + return base; + }; + + autoExpand = function () { + if (options.autoExpand && !autoExpandThrottle) { + autoExpandThrottle = setTimeout(base.expandToContent, 200); + } + }; + + /** + * Expands or shrinks the editors height to the height of it's content + * + * Unless ignoreMaxHeight is set to true it will not expand + * higher than the maxHeight option. + * + * @since 1.3.5 + * @param {boolean} [ignoreMaxHeight=false] + * @function + * @name expandToContent + * @memberOf SCEditor.prototype + * @see #resizeToContent + */ + base.expandToContent = function (ignoreMaxHeight) { + if (base.maximize()) { + return; + } + + clearTimeout(autoExpandThrottle); + autoExpandThrottle = false; + + if (!autoExpandBounds) { + var height$$1 = options.resizeMinHeight || options.height || + height(original); + + autoExpandBounds = { + min: height$$1, + max: options.resizeMaxHeight || (height$$1 * 2) + }; + } + + var range = globalDoc.createRange(); + range.selectNodeContents(wysiwygBody); + + var rect = range.getBoundingClientRect(); + var current = wysiwygDocument.documentElement.clientHeight - 1; + var spaceNeeded = rect.bottom - rect.top; + var newHeight = base.height() + 1 + (spaceNeeded - current); + + if (!ignoreMaxHeight && autoExpandBounds.max !== -1) { + newHeight = Math.min(newHeight, autoExpandBounds.max); + } + + base.height(Math.ceil(Math.max(newHeight, autoExpandBounds.min))); + }; + + /** + * Destroys the editor, removing all elements and + * event handlers. + * + * Leaves only the original textarea. + * + * @function + * @name destroy + * @memberOf SCEditor.prototype + */ + base.destroy = function () { + // Don't destroy if the editor has already been destroyed + if (!pluginManager) { + return; + } + + pluginManager.destroy(); + + rangeHelper = null; + lastRange = null; + pluginManager = null; + + if (dropdown) { + remove(dropdown); + } + + off(globalDoc, 'click', handleDocumentClick); + + // TODO: make off support null nodes? + var form = original.form; + if (form) { + off(form, 'reset', handleFormReset); + off(form, 'submit', base.updateOriginal); + } + + remove(sourceEditor); + remove(toolbar); + remove(editorContainer); + + delete original._sceditor; + show(original); + + original.required = isRequired; + }; + + + /** + * Creates a menu item drop down + * + * @param {HTMLElement} menuItem The button to align the dropdown with + * @param {string} name Used for styling the dropdown, will be + * a class sceditor-name + * @param {HTMLElement} content The HTML content of the dropdown + * @param {boolean} ieFix If to add the unselectable attribute + * to all the contents elements. Stops + * IE from deselecting the text in the + * editor + * @function + * @name createDropDown + * @memberOf SCEditor.prototype + */ + base.createDropDown = function (menuItem, name, content, ieFix) { + // first click for create second click for close + var dropDownCss, + dropDownClass = 'sceditor-' + name; + + // Will re-focus the editor. This is needed for IE + // as it has special logic to save/restore the selection + base.closeDropDown(true); + + // Only close the dropdown if it was already open + if (dropdown && hasClass(dropdown, dropDownClass)) { + return; + } + + // IE needs unselectable attr to stop it from + // unselecting the text in the editor. + // SCEditor can cope if IE does unselect the + // text it's just not nice. + if (ieFix !== false) { + each(find(content, ':not(input):not(textarea)'), + function (_, node) { + if (node.nodeType === ELEMENT_NODE) { + attr(node, 'unselectable', 'on'); + } + }); + } + + dropDownCss = extend({ + top: menuItem.offsetTop, + left: menuItem.offsetLeft, + marginTop: menuItem.clientHeight + }, options.dropDownCss); + + dropdown = createElement('div', { + className: 'sceditor-dropdown ' + dropDownClass + }); + + css(dropdown, dropDownCss); + appendChild(dropdown, content); + appendChild(editorContainer, dropdown); + on(dropdown, 'click focusin', function (e) { + // stop clicks within the dropdown from being handled + e.stopPropagation(); + }); + + // If try to focus the first input immediately IE will + // place the cursor at the start of the editor instead + // of focusing on the input. + setTimeout(function () { + if (dropdown) { + var first = find(dropdown, 'input,textarea')[0]; + if (first) { + first.focus(); + } + } + }); + }; + + /** + * Handles any document click and closes the dropdown if open + * @private + */ + handleDocumentClick = function (e) { + // ignore right clicks + if (e.which !== 3 && dropdown && !e.defaultPrevented) { + autoUpdate(); + + base.closeDropDown(); + } + }; + + /** + * Handles the WYSIWYG editors paste event + * @private + */ + handlePasteEvt = function (e) { + var isIeOrEdge = IE_VER || edge; + var editable = wysiwygBody; + var clipboard = e.clipboardData; + var loadImage = function (file) { + var reader = new FileReader(); + reader.onload = function (e) { + handlePasteData({ + html: '' + }); + }; + reader.readAsDataURL(file); + }; + + // Modern browsers with clipboard API - everything other than _very_ + // old android web views and UC browser which doesn't support the + // paste event at all. + if (clipboard && !isIeOrEdge) { + var data$$1 = {}; + var types = clipboard.types; + var items = clipboard.items; + + e.preventDefault(); + + for (var i = 0; i < types.length; i++) { + // Normalise image pasting to paste as a data-uri + if (globalWin.FileReader && items && + IMAGE_MIME_REGEX.test(items[i].type)) { + return loadImage(clipboard.items[i].getAsFile()); + } + + data$$1[types[i]] = clipboard.getData(types[i]); + } + // Call plugins here with file? + data$$1.text = data$$1['text/plain']; + data$$1.html = data$$1['text/html']; + + handlePasteData(data$$1); + // If contentsFragment exists then we are already waiting for a + // previous paste so let the handler for that handle this one too + } else if (!pasteContentFragment) { + // Save the scroll position so can be restored + // when contents is restored + var scrollTop = editable.scrollTop; + + rangeHelper.saveRange(); + + pasteContentFragment = globalDoc.createDocumentFragment(); + while (editable.firstChild) { + appendChild(pasteContentFragment, editable.firstChild); + } + + setTimeout(function () { + var html = editable.innerHTML; + + editable.innerHTML = ''; + appendChild(editable, pasteContentFragment); + editable.scrollTop = scrollTop; + pasteContentFragment = false; + + rangeHelper.restoreRange(); + + handlePasteData({ html: html }); + }, 0); + } + }; + + /** + * Gets the pasted data, filters it and then inserts it. + * @param {Object} data + * @private + */ + handlePasteData = function (data$$1) { + var pasteArea = createElement('div', {}, wysiwygDocument); + + pluginManager.call('pasteRaw', data$$1); + trigger(editorContainer, 'pasteraw', data$$1); + + if (data$$1.html) { + pasteArea.innerHTML = data$$1.html; + + // fix any invalid nesting + fixNesting(pasteArea); + } else { + pasteArea.innerHTML = entities(data$$1.text || ''); + } + + var paste = { + val: pasteArea.innerHTML + }; + + if ('fragmentToSource' in format) { + paste.val = format + .fragmentToSource(paste.val, wysiwygDocument, currentNode); + } + + pluginManager.call('paste', paste); + trigger(editorContainer, 'paste', paste); + + if ('fragmentToHtml' in format) { + paste.val = format + .fragmentToHtml(paste.val, currentNode); + } + + pluginManager.call('pasteHtml', paste); + + base.wysiwygEditorInsertHtml(paste.val, null, true); + }; + + /** + * Closes any currently open drop down + * + * @param {boolean} [focus=false] If to focus the editor + * after closing the drop down + * @function + * @name closeDropDown + * @memberOf SCEditor.prototype + */ + base.closeDropDown = function (focus) { + if (dropdown) { + remove(dropdown); + dropdown = null; + } + + if (focus === true) { + base.focus(); + } + }; + + + /** + * Inserts HTML into WYSIWYG editor. + * + * If endHtml is specified, any selected text will be placed + * between html and endHtml. If there is no selected text html + * and endHtml will just be concatenate together. + * + * @param {string} html + * @param {string} [endHtml=null] + * @param {boolean} [overrideCodeBlocking=false] If to insert the html + * into code tags, by + * default code tags only + * support text. + * @function + * @name wysiwygEditorInsertHtml + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertHtml = function ( + html, endHtml, overrideCodeBlocking + ) { + var marker, scrollTop, scrollTo, + editorHeight = height(wysiwygEditor); + + base.focus(); + + // TODO: This code tag should be configurable and + // should maybe convert the HTML into text instead + // Don't apply to code elements + if (!overrideCodeBlocking && closest(currentBlockNode, 'code')) { + return; + } + + // Insert the HTML and save the range so the editor can be scrolled + // to the end of the selection. Also allows emoticons to be replaced + // without affecting the cursor position + rangeHelper.insertHTML(html, endHtml); + rangeHelper.saveRange(); + replaceEmoticons(); + + // Scroll the editor after the end of the selection + marker = find(wysiwygBody, '#sceditor-end-marker')[0]; + show(marker); + scrollTop = wysiwygBody.scrollTop; + scrollTo = (getOffset(marker).top + + (marker.offsetHeight * 1.5)) - editorHeight; + hide(marker); + + // Only scroll if marker isn't already visible + if (scrollTo > scrollTop || scrollTo + editorHeight < scrollTop) { + wysiwygBody.scrollTop = scrollTo; + } + + triggerValueChanged(false); + rangeHelper.restoreRange(); + + // Add a new line after the last block element + // so can always add text after it + appendNewLine(); + }; + + /** + * Like wysiwygEditorInsertHtml except it will convert any HTML + * into text before inserting it. + * + * @param {string} text + * @param {string} [endText=null] + * @function + * @name wysiwygEditorInsertText + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertText = function (text, endText) { + base.wysiwygEditorInsertHtml( + entities(text), entities(endText) + ); + }; + + /** + * Inserts text into the WYSIWYG or source editor depending on which + * mode the editor is in. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.3.5 + * @function + * @name insertText + * @memberOf SCEditor.prototype + */ + base.insertText = function (text, endText) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(text, endText); + } else { + base.wysiwygEditorInsertText(text, endText); + } + + return base; + }; + + /** + * Like wysiwygEditorInsertHtml but inserts text into the + * source mode editor instead. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * The cursor will be placed after the text param. If endText is + * specified the cursor will be placed before endText, so passing:
    + * + * '[b]', '[/b]' + * + * Would cause the cursor to be placed:
    + * + * [b]Selected text|[/b] + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.4.0 + * @function + * @name sourceEditorInsertText + * @memberOf SCEditor.prototype + */ + base.sourceEditorInsertText = function (text, endText) { + var scrollTop, currentValue, + startPos = sourceEditor.selectionStart, + endPos = sourceEditor.selectionEnd; + + scrollTop = sourceEditor.scrollTop; + sourceEditor.focus(); + currentValue = sourceEditor.value; + + if (endText) { + text += currentValue.substring(startPos, endPos) + endText; + } + + sourceEditor.value = currentValue.substring(0, startPos) + + text + + currentValue.substring(endPos, currentValue.length); + + sourceEditor.selectionStart = (startPos + text.length) - + (endText ? endText.length : 0); + sourceEditor.selectionEnd = sourceEditor.selectionStart; + + sourceEditor.scrollTop = scrollTop; + sourceEditor.focus(); + + triggerValueChanged(); + }; + + /** + * Gets the current instance of the rangeHelper class + * for the editor. + * + * @return {RangeHelper} + * @function + * @name getRangeHelper + * @memberOf SCEditor.prototype + */ + base.getRangeHelper = function () { + return rangeHelper; + }; + + /** + * Gets or sets the source editor caret position. + * + * @param {Object} [position] + * @return {this} + * @function + * @since 1.4.5 + * @name sourceEditorCaret + * @memberOf SCEditor.prototype + */ + base.sourceEditorCaret = function (position) { + sourceEditor.focus(); + + if (position) { + sourceEditor.selectionStart = position.start; + sourceEditor.selectionEnd = position.end; + + return this; + } + + return { + start: sourceEditor.selectionStart, + end: sourceEditor.selectionEnd + }; + }; + + /** + * Gets the value of the editor. + * + * If the editor is in WYSIWYG mode it will return the filtered + * HTML from it (converted to BBCode if using the BBCode plugin). + * It it's in Source Mode it will return the unfiltered contents + * of the source editor (if using the BBCode plugin this will be + * BBCode again). + * + * @since 1.3.5 + * @return {string} + * @function + * @name val + * @memberOf SCEditor.prototype + */ + /** + * Sets the value of the editor. + * + * If filter set true the val will be passed through the filter + * function. If using the BBCode plugin it will pass the val to + * the BBCode filter to convert any BBCode into HTML. + * + * @param {string} val + * @param {boolean} [filter=true] + * @return {this} + * @since 1.3.5 + * @function + * @name val^2 + * @memberOf SCEditor.prototype + */ + base.val = function (val, filter) { + if (!isString(val)) { + return base.inSourceMode() ? + base.getSourceEditorValue(false) : + base.getWysiwygEditorValue(filter); + } + + if (!base.inSourceMode()) { + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + base.setWysiwygEditorValue(val); + } else { + base.setSourceEditorValue(val); + } + + return base; + }; + + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @return {this} + * @since 1.3.5 + * @function + * @name insert + * @memberOf SCEditor.prototype + */ + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * If the allowMixed param is set to true, HTML any will not be + * escaped + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @param {boolean} [allowMixed=false] + * @return {this} + * @since 1.4.3 + * @function + * @name insert^2 + * @memberOf SCEditor.prototype + */ + // eslint-disable-next-line max-params + base.insert = function ( + start, end, filter, convertEmoticons, allowMixed + ) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(start, end); + return base; + } + + // Add the selection between start and end + if (end) { + var html = rangeHelper.selectedHtml(); + + if (filter !== false && 'fragmentToSource' in format) { + html = format + .fragmentToSource(html, wysiwygDocument, currentNode); + } + + start += html + end; + } + // TODO: This filter should allow empty tags as it's inserting. + if (filter !== false && 'fragmentToHtml' in format) { + start = format.fragmentToHtml(start, currentNode); + } + + // Convert any escaped HTML back into HTML if mixed is allowed + if (filter !== false && allowMixed === true) { + start = start.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + } + + base.wysiwygEditorInsertHtml(start); + + return base; + }; + + /** + * Gets the WYSIWYG editors HTML value. + * + * If using a plugin that filters the Ht Ml like the BBCode plugin + * it will return the result of the filtering (BBCode) unless the + * filter param is set to false. + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @name getWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.getWysiwygEditorValue = function (filter) { + var html; + // Create a tmp node to store contents so it can be modified + // without affecting anything else. + var tmp = createElement('div', {}, wysiwygDocument); + var childNodes = wysiwygBody.childNodes; + + for (var i = 0; i < childNodes.length; i++) { + appendChild(tmp, childNodes[i].cloneNode(true)); + } + + appendChild(wysiwygBody, tmp); + fixNesting(tmp); + remove(tmp); + + html = tmp.innerHTML; + + // filter the HTML and DOM through any plugins + if (filter !== false && format.hasOwnProperty('toSource')) { + html = format.toSource(html, wysiwygDocument); + } + + return html; + }; + + /** + * Gets the WYSIWYG editor's iFrame Body. + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getBody + * @memberOf SCEditor.prototype + */ + base.getBody = function () { + return wysiwygBody; + }; + + /** + * Gets the WYSIWYG editors container area (whole iFrame). + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getContentAreaContainer + * @memberOf SCEditor.prototype + */ + base.getContentAreaContainer = function () { + return wysiwygEditor; + }; + + /** + * Gets the text editor value + * + * If using a plugin that filters the text like the BBCode plugin + * it will return the result of the filtering which is BBCode to + * HTML so it will return HTML. If filter is set to false it will + * just return the contents of the source editor (BBCode). + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @since 1.4.0 + * @name getSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.getSourceEditorValue = function (filter) { + var val = sourceEditor.value; + + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + return val; + }; + + /** + * Sets the WYSIWYG HTML editor value. Should only be the HTML + * contained within the body tags + * + * @param {string} value + * @function + * @name setWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.setWysiwygEditorValue = function (value) { + if (!value) { + value = '

    ' + (IE_VER ? '' : '
    ') + '

    '; + } + + wysiwygBody.innerHTML = value; + replaceEmoticons(); + + appendNewLine(); + triggerValueChanged(); + autoExpand(); + }; + + /** + * Sets the text editor value + * + * @param {string} value + * @function + * @name setSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.setSourceEditorValue = function (value) { + sourceEditor.value = value; + + triggerValueChanged(); + }; + + /** + * Updates the textarea that the editor is replacing + * with the value currently inside the editor. + * + * @function + * @name updateOriginal + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.updateOriginal = function () { + original.value = base.val(); + }; + + /** + * Replaces any emoticon codes in the passed HTML + * with their emoticon images + * @private + */ + replaceEmoticons = function () { + if (options.emoticonsEnabled) { + replace(wysiwygBody, allEmoticons, options.emoticonsCompat); + } + }; + + /** + * If the editor is in source code mode + * + * @return {boolean} + * @function + * @name inSourceMode + * @memberOf SCEditor.prototype + */ + base.inSourceMode = function () { + return hasClass(editorContainer, 'sourceMode'); + }; + + /** + * Gets if the editor is in sourceMode + * + * @return boolean + * @function + * @name sourceMode + * @memberOf SCEditor.prototype + */ + /** + * Sets if the editor is in sourceMode + * + * @param {boolean} enable + * @return {this} + * @function + * @name sourceMode^2 + * @memberOf SCEditor.prototype + */ + base.sourceMode = function (enable) { + var inSourceMode = base.inSourceMode(); + + if (typeof enable !== 'boolean') { + return inSourceMode; + } + + if ((inSourceMode && !enable) || (!inSourceMode && enable)) { + base.toggleSourceMode(); + } + + return base; + }; + + /** + * Switches between the WYSIWYG and source modes + * + * @function + * @name toggleSourceMode + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.toggleSourceMode = function () { + var isInSourceMode = base.inSourceMode(); + + // don't allow switching to WYSIWYG if doesn't support it + if (!isWysiwygSupported && isInSourceMode) { + return; + } + + if (!isInSourceMode) { + rangeHelper.saveRange(); + rangeHelper.clear(); + } + + base.blur(); + + if (isInSourceMode) { + base.setWysiwygEditorValue(base.getSourceEditorValue()); + } else { + base.setSourceEditorValue(base.getWysiwygEditorValue()); + } + + lastRange = null; + toggle(sourceEditor); + toggle(wysiwygEditor); + + toggleClass(editorContainer, 'wysiwygMode', isInSourceMode); + toggleClass(editorContainer, 'sourceMode', !isInSourceMode); + + updateToolBar(); + updateActiveButtons(); + }; + + /** + * Gets the selected text of the source editor + * @return {string} + * @private + */ + sourceEditorSelectedText = function () { + sourceEditor.focus(); + + return sourceEditor.value.substring( + sourceEditor.selectionStart, + sourceEditor.selectionEnd + ); + }; + + /** + * Handles the passed command + * @private + */ + handleCommand = function (caller, cmd) { + // check if in text mode and handle text commands + if (base.inSourceMode()) { + if (cmd.txtExec) { + if (Array.isArray(cmd.txtExec)) { + base.sourceEditorInsertText.apply(base, cmd.txtExec); + } else { + cmd.txtExec.call(base, caller, sourceEditorSelectedText()); + } + } + } else if (cmd.exec) { + if (isFunction(cmd.exec)) { + cmd.exec.call(base, caller); + } else { + base.execCommand( + cmd.exec, + cmd.hasOwnProperty('execParam') ? cmd.execParam : null + ); + } + } + + }; + + /** + * Saves the current range. Needed for IE because it forgets + * where the cursor was and what was selected + * @private + */ + saveRange = function () { + /* this is only needed for IE */ + if (IE_VER) { + lastRange = rangeHelper.selectedRange(); + } + }; + + /** + * Executes a command on the WYSIWYG editor + * + * @param {string} command + * @param {String|Boolean} [param] + * @function + * @name execCommand + * @memberOf SCEditor.prototype + */ + base.execCommand = function (command, param) { + var executed = false, + commandObj = base.commands[command]; + + base.focus(); + + // TODO: make configurable + // don't apply any commands to code elements + if (closest(rangeHelper.parentNode(), 'code')) { + return; + } + + try { + executed = wysiwygDocument.execCommand(command, false, param); + } catch (ex) { } + + // show error if execution failed and an error message exists + if (!executed && commandObj && commandObj.errorMessage) { + /*global alert:false*/ + alert(base._(commandObj.errorMessage)); + } + + updateActiveButtons(); + }; + + /** + * Checks if the current selection has changed and triggers + * the selectionchanged event if it has. + * + * In browsers other than IE, it will check at most once every 100ms. + * This is because only IE has a selection changed event. + * @private + */ + checkSelectionChanged = function () { + function check() { + // Don't create new selection if there isn't one (like after + // blur event in iOS) + if (wysiwygWindow.getSelection() && + wysiwygWindow.getSelection().rangeCount <= 0) { + currentSelection = null; + // rangeHelper could be null if editor was destroyed + // before the timeout had finished + } else if (rangeHelper && !rangeHelper.compare(currentSelection)) { + currentSelection = rangeHelper.cloneSelected(); + + // If the selection is in an inline wrap it in a block. + // Fixes #331 + if (currentSelection && currentSelection.collapsed) { + var parent$$1 = currentSelection.startContainer; + var offset = currentSelection.startOffset; + + // Handle if selection is placed before/after an element + if (offset && parent$$1.nodeType !== TEXT_NODE) { + parent$$1 = parent$$1.childNodes[offset]; + } + + while (parent$$1 && parent$$1.parentNode !== wysiwygBody) { + parent$$1 = parent$$1.parentNode; + } + + if (parent$$1 && isInline(parent$$1, true)) { + rangeHelper.saveRange(); + wrapInlines(wysiwygBody, wysiwygDocument); + rangeHelper.restoreRange(); + } + } + + trigger(editorContainer, 'selectionchanged'); + } + + isSelectionCheckPending = false; + } + + if (isSelectionCheckPending) { + return; + } + + isSelectionCheckPending = true; + + // Don't need to limit checking if browser supports the Selection API + if ('onselectionchange' in wysiwygDocument) { + check(); + } else { + setTimeout(check, 100); + } + }; + + /** + * Checks if the current node has changed and triggers + * the nodechanged event if it has + * @private + */ + checkNodeChanged = function () { + // check if node has changed + var oldNode, + node = rangeHelper.parentNode(); + + if (currentNode !== node) { + oldNode = currentNode; + currentNode = node; + currentBlockNode = rangeHelper.getFirstBlockParent(node); + + trigger(editorContainer, 'nodechanged', { + oldNode: oldNode, + newNode: currentNode + }); + } + }; + + /** + * Gets the current node that contains the selection/caret in + * WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentNode + * @memberOf SCEditor.prototype + */ + base.currentNode = function () { + return currentNode; + }; + + /** + * Gets the first block level node that contains the + * selection/caret in WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentBlockNode + * @memberOf SCEditor.prototype + * @since 1.4.4 + */ + base.currentBlockNode = function () { + return currentBlockNode; + }; + + /** + * Updates if buttons are active or not + * @private + */ + updateActiveButtons = function () { + var firstBlock, parent$$1; + var activeClass = 'active'; + var doc = wysiwygDocument; + var isSource = base.sourceMode(); + + if (base.readOnly()) { + each(find(toolbar, activeClass), function (_, menuItem) { + removeClass(menuItem, activeClass); + }); + return; + } + + if (!isSource) { + parent$$1 = rangeHelper.parentNode(); + firstBlock = rangeHelper.getFirstBlockParent(parent$$1); + } + + for (var j = 0; j < btnStateHandlers.length; j++) { + var state = 0; + var btn = toolbarButtons[btnStateHandlers[j].name]; + var stateFn = btnStateHandlers[j].state; + var isDisabled = (isSource && !btn._sceTxtMode) || + (!isSource && !btn._sceWysiwygMode); + + if (isString(stateFn)) { + if (!isSource) { + try { + state = doc.queryCommandEnabled(stateFn) ? 0 : -1; + + // eslint-disable-next-line max-depth + if (state > -1) { + state = doc.queryCommandState(stateFn) ? 1 : 0; + } + } catch (ex) {} + } + } else if (!isDisabled) { + state = stateFn.call(base, parent$$1, firstBlock); + } + + toggleClass(btn, 'disabled', isDisabled || state < 0); + toggleClass(btn, activeClass, state > 0); + } + + if (icons && icons.update) { + icons.update(isSource, parent$$1, firstBlock); + } + }; + + /** + * Handles any key press in the WYSIWYG editor + * + * @private + */ + handleKeyPress = function (e) { + // FF bug: https://bugzilla.mozilla.org/show_bug.cgi?id=501496 + if (e.defaultPrevented) { + return; + } + + base.closeDropDown(); + + // 13 = enter key + if (e.which === 13) { + var LIST_TAGS = 'li,ul,ol'; + + // "Fix" (cludge) for blocklevel elements being duplicated in some + // browsers when enter is pressed instead of inserting a newline + if (!is(currentBlockNode, LIST_TAGS) && + hasStyling(currentBlockNode)) { + lastRange = null; + + var br = createElement('br', {}, wysiwygDocument); + rangeHelper.insertNode(br); + + // Last
    of a block will be collapsed unless it is + // IE < 11 so need to make sure the
    that was inserted + // isn't the last node of a block. + if (!IE_BR_FIX$2) { + var parent$$1 = br.parentNode; + var lastChild = parent$$1.lastChild; + + // Sometimes an empty next node is created after the
    + if (lastChild && lastChild.nodeType === TEXT_NODE && + lastChild.nodeValue === '') { + remove(lastChild); + lastChild = parent$$1.lastChild; + } + + // If this is the last BR of a block and the previous + // sibling is inline then will need an extra BR. This + // is needed because the last BR of a block will be + // collapsed. Fixes issue #248 + if (!isInline(parent$$1, true) && lastChild === br && + isInline(br.previousSibling)) { + rangeHelper.insertHTML('
    '); + } + } + + e.preventDefault(); + } + } + }; + + /** + * Makes sure that if there is a code or quote tag at the + * end of the editor, that there is a new line after it. + * + * If there wasn't a new line at the end you wouldn't be able + * to enter any text after a code/quote tag + * @return {void} + * @private + */ + appendNewLine = function () { + // Check all nodes in reverse until either add a new line + // or reach a non-empty textnode or BR at which point can + // stop checking. + rTraverse(wysiwygBody, function (node) { + // Last block, add new line after if has styling + if (node.nodeType === ELEMENT_NODE && + !/inline/.test(css(node, 'display'))) { + + // Add line break after if has styling + if (!is(node, '.sceditor-nlf') && hasStyling(node)) { + var paragraph = createElement('p', {}, wysiwygDocument); + paragraph.className = 'sceditor-nlf'; + paragraph.innerHTML = !IE_BR_FIX$2 ? '
    ' : ''; + appendChild(wysiwygBody, paragraph); + return false; + } + } + + // Last non-empty text node or line break. + // No need to add line-break after them + if ((node.nodeType === 3 && !/^\s*$/.test(node.nodeValue)) || + is(node, 'br')) { + return false; + } + }); + }; + + /** + * Handles form reset event + * @private + */ + handleFormReset = function () { + base.val(original.value); + }; + + /** + * Handles any mousedown press in the WYSIWYG editor + * @private + */ + handleMouseDown = function () { + base.closeDropDown(); + lastRange = null; + }; + + /** + * Translates the string into the locale language. + * + * Replaces any {0}, {1}, {2}, ect. with the params provided. + * + * @param {string} str + * @param {...String} args + * @return {string} + * @function + * @name _ + * @memberOf SCEditor.prototype + */ + base._ = function () { + var undef, + args = arguments; + + if (locale && locale[args[0]]) { + args[0] = locale[args[0]]; + } + + return args[0].replace(/\{(\d+)\}/g, function (str, p1) { + return args[p1 - 0 + 1] !== undef ? + args[p1 - 0 + 1] : + '{' + p1 + '}'; + }); + }; + + /** + * Passes events on to any handlers + * @private + * @return void + */ + handleEvent = function (e) { + if (pluginManager) { + // Send event to all plugins + pluginManager.call(e.type + 'Event', e, base); + } + + // convert the event into a custom event to send + var name = (e.target === sourceEditor ? 'scesrc' : 'scewys') + e.type; + + if (eventHandlers[name]) { + eventHandlers[name].forEach(function (fn) { + fn.call(base, e); + }); + } + }; + + /** + * Binds a handler to the specified events + * + * This function only binds to a limited list of + * supported events. + * + * The supported events are: + * + * * keyup + * * keydown + * * Keypress + * * blur + * * focus + * * nodechanged - When the current node containing + * the selection changes in WYSIWYG mode + * * contextmenu + * * selectionchanged + * * valuechanged + * + * + * The events param should be a string containing the event(s) + * to bind this handler to. If multiple, they should be separated + * by spaces. + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name bind + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.bind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + var wysEvent = 'scewys' + events[i]; + var srcEvent = 'scesrc' + events[i]; + // Use custom events to allow passing the instance as the + // 2nd argument. + // Also allows unbinding without unbinding the editors own + // event handlers. + if (!excludeWysiwyg) { + eventHandlers[wysEvent] = eventHandlers[wysEvent] || []; + eventHandlers[wysEvent].push(handler); + } + + if (!excludeSource) { + eventHandlers[srcEvent] = eventHandlers[srcEvent] || []; + eventHandlers[srcEvent].push(handler); + } + + // Start sending value changed events + if (events[i] === 'valuechanged') { + triggerValueChanged.hasHandler = true; + } + } + } + + return base; + }; + + /** + * Unbinds an event that was bound using bind(). + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude unbinding this + * handler from the WYSIWYG editor + * @param {boolean} excludeSource if to exclude unbinding this + * handler from the source editor + * @return {this} + * @function + * @name unbind + * @memberOf SCEditor.prototype + * @since 1.4.1 + * @see bind + */ + base.unbind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + if (!excludeWysiwyg) { + arrayRemove( + eventHandlers['scewys' + events[i]] || [], handler); + } + + if (!excludeSource) { + arrayRemove( + eventHandlers['scesrc' + events[i]] || [], handler); + } + } + } + + return base; + }; + + /** + * Blurs the editors input area + * + * @return {this} + * @function + * @name blur + * @memberOf SCEditor.prototype + * @since 1.3.6 + */ + /** + * Adds a handler to the editors blur event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name blur^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.blur = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('blur', handler, excludeWysiwyg, excludeSource); + } else if (!base.sourceMode()) { + wysiwygBody.blur(); + } else { + sourceEditor.blur(); + } + + return base; + }; + + /** + * Focuses the editors input area + * + * @return {this} + * @function + * @name focus + * @memberOf SCEditor.prototype + */ + /** + * Adds an event handler to the focus event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name focus^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.focus = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('focus', handler, excludeWysiwyg, excludeSource); + } else if (!base.inSourceMode()) { + // Already has focus so do nothing + if (find(wysiwygDocument, ':focus').length) { + return; + } + + var container; + var rng = rangeHelper.selectedRange(); + + // Fix FF bug where it shows the cursor in the wrong place + // if the editor hasn't had focus before. See issue #393 + if (!currentSelection) { + autofocus(); + } + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$2 && rng && rng.endOffset === 1 && rng.collapsed) { + container = rng.endContainer; + + if (container && container.childNodes.length === 1 && + is(container.firstChild, 'br')) { + rng.setStartBefore(container.firstChild); + rng.collapse(true); + rangeHelper.selectRange(rng); + } + } + + wysiwygWindow.focus(); + wysiwygBody.focus(); + + // Needed for IE + if (lastRange) { + rangeHelper.selectRange(lastRange); + + // Remove the stored range after being set. + // If the editor loses focus it should be saved again. + lastRange = null; + } + } else { + sourceEditor.focus(); + } + + updateActiveButtons(); + + return base; + }; + + /** + * Adds a handler to the key down event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyDown + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyDown = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keydown', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key press event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyPress + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyPress = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('keypress', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key up event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyUp + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyUp = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keyup', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the node changed event. + * + * Happens whenever the node containing the selection/caret + * changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name nodeChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.nodeChanged = function (handler) { + return base.bind('nodechanged', handler, false, true); + }; + + /** + * Adds a handler to the selection changed event + * + * Happens whenever the selection changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name selectionChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.selectionChanged = function (handler) { + return base.bind('selectionchanged', handler, false, true); + }; + + /** + * Adds a handler to the value changed event + * + * Happens whenever the current editor value changes. + * + * Whenever anything is inserted, the value changed or + * 1.5 secs after text is typed. If a space is typed it will + * cause the event to be triggered immediately instead of + * after 1.5 seconds + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name valueChanged + * @memberOf SCEditor.prototype + * @since 1.4.5 + */ + base.valueChanged = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('valuechanged', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Emoticons keypress handler + * @private + */ + emoticonsKeyPress = function (e) { + var replacedEmoticon, + cachePos = 0, + emoticonsCache = base.emoticonsCache, + curChar = String.fromCharCode(e.which); + + // TODO: Make configurable + if (closest(currentBlockNode, 'code')) { + return; + } + + if (!emoticonsCache) { + emoticonsCache = []; + + each(allEmoticons, function (key, html) { + emoticonsCache[cachePos++] = [key, html]; + }); + + emoticonsCache.sort(function (a, b) { + return a[0].length - b[0].length; + }); + + base.emoticonsCache = emoticonsCache; + base.longestEmoticonCode = + emoticonsCache[emoticonsCache.length - 1][0].length; + } + + replacedEmoticon = rangeHelper.replaceKeyword( + base.emoticonsCache, + true, + true, + base.longestEmoticonCode, + options.emoticonsCompat, + curChar + ); + + if (replacedEmoticon) { + if (!options.emoticonsCompat || !/^\s$/.test(curChar)) { + e.preventDefault(); + } + } + }; + + /** + * Makes sure emoticons are surrounded by whitespace + * @private + */ + emoticonsCheckWhitespace = function () { + checkWhitespace(currentBlockNode, rangeHelper); + }; + + /** + * Gets if emoticons are currently enabled + * @return {boolean} + * @function + * @name emoticons + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + /** + * Enables/disables emoticons + * + * @param {boolean} enable + * @return {this} + * @function + * @name emoticons^2 + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + base.emoticons = function (enable) { + if (!enable && enable !== false) { + return options.emoticonsEnabled; + } + + options.emoticonsEnabled = enable; + + if (enable) { + on(wysiwygBody, 'keypress', emoticonsKeyPress); + + if (!base.sourceMode()) { + rangeHelper.saveRange(); + + replaceEmoticons(); + triggerValueChanged(false); + + rangeHelper.restoreRange(); + } + } else { + var emoticons = + find(wysiwygBody, 'img[data-sceditor-emoticon]'); + + each(emoticons, function (_, img) { + var text = data(img, 'sceditor-emoticon'); + var textNode = wysiwygDocument.createTextNode(text); + img.parentNode.replaceChild(textNode, img); + }); + + off(wysiwygBody, 'keypress', emoticonsKeyPress); + + triggerValueChanged(); + } + + return base; + }; + + /** + * Gets the current WYSIWYG editors inline CSS + * + * @return {string} + * @function + * @name css + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + /** + * Sets inline CSS for the WYSIWYG editor + * + * @param {string} css + * @return {this} + * @function + * @name css^2 + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + base.css = function (css$$1) { + if (!inlineCss) { + inlineCss = createElement('style', { + id: 'inline' + }, wysiwygDocument); + + appendChild(wysiwygDocument.head, inlineCss); + } + + if (!isString(css$$1)) { + return inlineCss.styleSheet ? + inlineCss.styleSheet.cssText : inlineCss.innerHTML; + } + + if (inlineCss.styleSheet) { + inlineCss.styleSheet.cssText = css$$1; + } else { + inlineCss.innerHTML = css$$1; + } + + return base; + }; + + /** + * Handles the keydown event, used for shortcuts + * @private + */ + handleKeyDown = function (e) { + var shortcut = [], + SHIFT_KEYS = { + '`': '~', + '1': '!', + '2': '@', + '3': '#', + '4': '$', + '5': '%', + '6': '^', + '7': '&', + '8': '*', + '9': '(', + '0': ')', + '-': '_', + '=': '+', + ';': ': ', + '\'': '"', + ',': '<', + '.': '>', + '/': '?', + '\\': '|', + '[': '{', + ']': '}' + }, + SPECIAL_KEYS = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 19: 'pause', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'insert', + 46: 'del', + 91: 'win', + 92: 'win', + 93: 'select', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9', + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111: '/', + 112: 'f1', + 113: 'f2', + 114: 'f3', + 115: 'f4', + 116: 'f5', + 117: 'f6', + 118: 'f7', + 119: 'f8', + 120: 'f9', + 121: 'f10', + 122: 'f11', + 123: 'f12', + 144: 'numlock', + 145: 'scrolllock', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }, + NUMPAD_SHIFT_KEYS = { + 109: '-', + 110: 'del', + 111: '/', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }, + which = e.which, + character = SPECIAL_KEYS[which] || + String.fromCharCode(which).toLowerCase(); + + if (e.ctrlKey || e.metaKey) { + shortcut.push('ctrl'); + } + + if (e.altKey) { + shortcut.push('alt'); + } + + if (e.shiftKey) { + shortcut.push('shift'); + + if (NUMPAD_SHIFT_KEYS[which]) { + character = NUMPAD_SHIFT_KEYS[which]; + } else if (SHIFT_KEYS[character]) { + character = SHIFT_KEYS[character]; + } + } + + // Shift is 16, ctrl is 17 and alt is 18 + if (character && (which < 16 || which > 18)) { + shortcut.push(character); + } + + shortcut = shortcut.join('+'); + if (shortcutHandlers[shortcut] && + shortcutHandlers[shortcut].call(base) === false) { + + e.stopPropagation(); + e.preventDefault(); + } + }; + + /** + * Adds a shortcut handler to the editor + * @param {string} shortcut + * @param {String|Function} cmd + * @return {sceditor} + */ + base.addShortcut = function (shortcut, cmd) { + shortcut = shortcut.toLowerCase(); + + if (isString(cmd)) { + shortcutHandlers[shortcut] = function () { + handleCommand(toolbarButtons[cmd], base.commands[cmd]); + + return false; + }; + } else { + shortcutHandlers[shortcut] = cmd; + } + + return base; + }; + + /** + * Removes a shortcut handler + * @param {string} shortcut + * @return {sceditor} + */ + base.removeShortcut = function (shortcut) { + delete shortcutHandlers[shortcut.toLowerCase()]; + + return base; + }; + + /** + * Handles the backspace key press + * + * Will remove block styling like quotes/code ect if at the start. + * @private + */ + handleBackSpace = function (e) { + var node, offset, range, parent$$1; + + // 8 is the backspace key + if (options.disableBlockRemove || e.which !== 8 || + !(range = rangeHelper.selectedRange())) { + return; + } + + node = range.startContainer; + offset = range.startOffset; + + if (offset !== 0 || !(parent$$1 = currentStyledBlockNode()) || + is(parent$$1, 'body')) { + return; + } + + while (node !== parent$$1) { + while (node.previousSibling) { + node = node.previousSibling; + + // Everything but empty text nodes before the cursor + // should prevent the style from being removed + if (node.nodeType !== TEXT_NODE || node.nodeValue) { + return; + } + } + + if (!(node = node.parentNode)) { + return; + } + } + + // The backspace was pressed at the start of + // the container so clear the style + base.clearBlockFormatting(parent$$1); + e.preventDefault(); + }; + + /** + * Gets the first styled block node that contains the cursor + * @return {HTMLElement} + */ + currentStyledBlockNode = function () { + var block = currentBlockNode; + + while (!hasStyling(block) || isInline(block, true)) { + if (!(block = block.parentNode) || is(block, 'body')) { + return; + } + } + + return block; + }; + + /** + * Clears the formatting of the passed block element. + * + * If block is false, if will clear the styling of the first + * block level element that contains the cursor. + * @param {HTMLElement} block + * @since 1.4.4 + */ + base.clearBlockFormatting = function (block) { + block = block || currentStyledBlockNode(); + + if (!block || is(block, 'body')) { + return base; + } + + rangeHelper.saveRange(); + + block.className = ''; + lastRange = null; + + attr(block, 'style', ''); + + if (!is(block, 'p,div,td')) { + convertElement(block, 'p'); + } + + rangeHelper.restoreRange(); + return base; + }; + + /** + * Triggers the valueChanged signal if there is + * a plugin that handles it. + * + * If rangeHelper.saveRange() has already been + * called, then saveRange should be set to false + * to prevent the range being saved twice. + * + * @since 1.4.5 + * @param {boolean} saveRange If to call rangeHelper.saveRange(). + * @private + */ + triggerValueChanged = function (saveRange) { + if (!pluginManager || + (!pluginManager.hasHandler('valuechangedEvent') && + !triggerValueChanged.hasHandler)) { + return; + } + + var currentHtml, + sourceMode = base.sourceMode(), + hasSelection = !sourceMode && rangeHelper.hasSelection(); + + // Composition end isn't guaranteed to fire but must have + // ended when triggerValueChanged() is called so reset it + isComposing = false; + + // Don't need to save the range if sceditor-start-marker + // is present as the range is already saved + saveRange = saveRange !== false && + !wysiwygDocument.getElementById('sceditor-start-marker'); + + // Clear any current timeout as it's now been triggered + if (valueChangedKeyUpTimer) { + clearTimeout(valueChangedKeyUpTimer); + valueChangedKeyUpTimer = false; + } + + if (hasSelection && saveRange) { + rangeHelper.saveRange(); + } + + currentHtml = sourceMode ? sourceEditor.value : wysiwygBody.innerHTML; + + // Only trigger if something has actually changed. + if (currentHtml !== triggerValueChanged.lastVal) { + triggerValueChanged.lastVal = currentHtml; + + trigger(editorContainer, 'valuechanged', { + rawValue: sourceMode ? base.val() : currentHtml + }); + } + + if (hasSelection && saveRange) { + rangeHelper.removeMarkers(); + } + }; + + /** + * Should be called whenever there is a blur event + * @private + */ + valueChangedBlur = function () { + if (valueChangedKeyUpTimer) { + triggerValueChanged(); + } + }; + + /** + * Should be called whenever there is a keypress event + * @param {Event} e The keypress event + * @private + */ + valueChangedKeyUp = function (e) { + var which = e.which, + lastChar = valueChangedKeyUp.lastChar, + lastWasSpace = (lastChar === 13 || lastChar === 32), + lastWasDelete = (lastChar === 8 || lastChar === 46); + + valueChangedKeyUp.lastChar = which; + + if (isComposing) { + return; + } + + // 13 = return & 32 = space + if (which === 13 || which === 32) { + if (!lastWasSpace) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + // 8 = backspace & 46 = del + } else if (which === 8 || which === 46) { + if (!lastWasDelete) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + } else if (valueChangedKeyUp.triggerNext) { + triggerValueChanged(); + valueChangedKeyUp.triggerNext = false; + } + + // Clear the previous timeout and set a new one. + clearTimeout(valueChangedKeyUpTimer); + + // Trigger the event 1.5s after the last keypress if space + // isn't pressed. This might need to be lowered, will need + // to look into what the slowest average Chars Per Min is. + valueChangedKeyUpTimer = setTimeout(function () { + if (!isComposing) { + triggerValueChanged(); + } + }, 1500); + }; + + handleComposition = function (e) { + isComposing = /start/i.test(e.type); + + if (!isComposing) { + triggerValueChanged(); + } + }; + + autoUpdate = function () { + base.updateOriginal(); + }; + + // run the initializer + init(); + } + + + /** + * Map containing the loaded SCEditor locales + * @type {Object} + * @name locale + * @memberOf sceditor + */ + SCEditor.locale = {}; + + SCEditor.formats = {}; + SCEditor.icons = {}; + + + /** + * Static command helper class + * @class command + * @name sceditor.command + */ + SCEditor.command = + /** @lends sceditor.command */ + { + /** + * Gets a command + * + * @param {string} name + * @return {Object|null} + * @since v1.3.5 + */ + get: function (name) { + return defaultCmds[name] || null; + }, + + /** + *

    Adds a command to the editor or updates an existing + * command if a command with the specified name already exists.

    + * + *

    Once a command is add it can be included in the toolbar by + * adding it's name to the toolbar option in the constructor. It + * can also be executed manually by calling + * {@link sceditor.execCommand}

    + * + * @example + * SCEditor.command.set("hello", + * { + * exec: function () { + * alert("Hello World!"); + * } + * }); + * + * @param {string} name + * @param {Object} cmd + * @return {this|false} Returns false if name or cmd is false + * @since v1.3.5 + */ + set: function (name, cmd) { + if (!name || !cmd) { + return false; + } + + // merge any existing command properties + cmd = extend(defaultCmds[name] || {}, cmd); + + cmd.remove = function () { + SCEditor.command.remove(name); + }; + + defaultCmds[name] = cmd; + return this; + }, + + /** + * Removes a command + * + * @param {string} name + * @return {this} + * @since v1.3.5 + */ + remove: function (name) { + if (defaultCmds[name]) { + delete defaultCmds[name]; + } + + return this; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + */ + + window.sceditor = { + command: SCEditor.command, + commands: defaultCmds, + defaultOptions: defaultOptions, + + ie: ie, + ios: ios, + isWysiwygSupported: isWysiwygSupported, + + regexEscape: regex, + escapeEntities: entities, + escapeUriScheme: uriScheme, + + dom: { + css: css, + attr: attr, + removeAttr: removeAttr, + is: is, + closest: closest, + width: width, + height: height, + traverse: traverse, + rTraverse: rTraverse, + parseHTML: parseHTML, + hasStyling: hasStyling, + convertElement: convertElement, + blockLevelList: blockLevelList, + canHaveChildren: canHaveChildren, + isInline: isInline, + copyCSS: copyCSS, + fixNesting: fixNesting, + findCommonAncestor: findCommonAncestor, + getSibling: getSibling, + removeWhiteSpace: removeWhiteSpace, + extractContents: extractContents, + getOffset: getOffset, + getStyle: getStyle, + hasStyle: hasStyle + }, + locale: SCEditor.locale, + icons: SCEditor.icons, + utils: { + each: each, + isEmptyObject: isEmptyObject, + extend: extend + }, + plugins: PluginManager.plugins, + formats: SCEditor.formats, + create: function (textarea, options) { + options = options || {}; + + // Don't allow the editor to be initialised + // on it's own source editor + if (parent(textarea, '.sceditor-container')) { + return; + } + + if (options.runWithoutWysiwygSupport || isWysiwygSupported) { + /*eslint no-new: off*/ + (new SCEditor(textarea, options)); + } + }, + instance: function (textarea) { + return textarea._sceditor; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + * @requires jQuery + */ + + // For backwards compatibility + $.sceditor = window.sceditor; + + /** + * Creates an instance of sceditor on all textareas + * matched by the jQuery selector. + * + * If options is set to "state" it will return bool value + * indicating if the editor has been initialised on the + * matched textarea(s). If there is only one textarea + * it will return the bool value for that textarea. + * If more than one textarea is matched it will + * return an array of bool values for each textarea. + * + * If options is set to "instance" it will return the + * current editor instance for the textarea(s). Like the + * state option, if only one textarea is matched this will + * return just the instance for that textarea. If more than + * one textarea is matched it will return an array of + * instances each textarea. + * + * @param {Object|string} [options] Should either be an Object of options or + * the strings "state" or "instance" + * @return {this|Array|Array|SCEditor|boolean} + */ + $.fn.sceditor = function (options) { + var instance; + var ret = []; + + this.each(function () { + instance = this._sceditor; + + // Add state of instance to ret if that is what options is set to + if (options === 'state') { + ret.push(!!instance); + } else if (options === 'instance') { + ret.push(instance); + } else if (!instance) { + $.sceditor.create(this, options); + } + }); + + // If nothing in the ret array then must be init so return this + if (!ret.length) { + return this; + } + + return ret.length === 1 ? ret[0] : ret; + }; + +}(jQuery)); +;/** + * SCEditor BBCode Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2011-2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor BBCode Format + * @author Sam Clarke + */ +(function (sceditor) { + /*eslint max-depth: off*/ + 'use strict'; + + var escapeEntities = sceditor.escapeEntities; + var escapeUriScheme = sceditor.escapeUriScheme; + var dom = sceditor.dom; + var utils = sceditor.utils; + + var css = dom.css; + var attr = dom.attr; + var is = dom.is; + var extend = utils.extend; + var each = utils.each; + + var IE_VER = sceditor.ie; + + // In IE < 11 a BR at the end of a block level element + // causes a double line break. + var IE_BR_FIX = IE_VER && IE_VER < 11; + + var EMOTICON_DATA_ATTR = 'data-sceditor-emoticon'; + + var getEditorCommand = sceditor.command.get; + + var QuoteType = { + /** @lends BBCodeParser.QuoteType */ + /** + * Always quote the attribute value + * @type {Number} + */ + always: 1, + + /** + * Never quote the attributes value + * @type {Number} + */ + never: 2, + + /** + * Only quote the attributes value when it contains spaces to equals + * @type {Number} + */ + auto: 3 + }; + + var defaultCommandsOverrides = { + bold: { + txtExec: ['[b]', '[/b]'] + }, + italic: { + txtExec: ['[i]', '[/i]'] + }, + underline: { + txtExec: ['[u]', '[/u]'] + }, + strike: { + txtExec: ['[s]', '[/s]'] + }, + subscript: { + txtExec: ['[sub]', '[/sub]'] + }, + superscript: { + txtExec: ['[sup]', '[/sup]'] + }, + left: { + txtExec: ['[left]', '[/left]'] + }, + center: { + txtExec: ['[center]', '[/center]'] + }, + right: { + txtExec: ['[right]', '[/right]'] + }, + justify: { + txtExec: ['[justify]', '[/justify]'] + }, + font: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('font')._dropDown( + editor, + caller, + function (fontName) { + editor.insertText( + '[font=' + fontName + ']', + '[/font]' + ); + } + ); + } + }, + size: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('size')._dropDown( + editor, + caller, + function (fontSize) { + editor.insertText( + '[size=' + fontSize + ']', + '[/size]' + ); + } + ); + } + }, + color: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('color')._dropDown( + editor, + caller, + function (color) { + editor.insertText( + '[color=' + color + ']', + '[/color]' + ); + } + ); + } + }, + bulletlist: { + txtExec: function (caller, selected) { + var content = ''; + + each(selected.split(/\r?\n/), function () { + content += (content ? '\n' : '') + + '[li]' + this + '[/li]'; + }); + + this.insertText('[ul]\n' + content + '\n[/ul]'); + } + }, + orderedlist: { + txtExec: function (caller, selected) { + var content = ''; + + each(selected.split(/\r?\n/), function () { + content += (content ? '\n' : '') + + '[li]' + this + '[/li]'; + }); + + this.insertText('[ol]\n' + content + '\n[/ol]'); + } + }, + table: { + txtExec: ['[table][tr][td]', '[/td][/tr][/table]'] + }, + horizontalrule: { + txtExec: ['[hr]'] + }, + code: { + txtExec: ['[code]', '[/code]'] + }, + image: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('image')._dropDown( + editor, + caller, + selected, + function (url, width, height) { + var attrs = ''; + + if (width) { + attrs += ' width=' + width; + } + + if (height) { + attrs += ' height=' + height; + } + + editor.insertText( + '[img' + attrs + ']' + url + '[/img]' + ); + } + ); + } + }, + email: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('email')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '[email=' + url + ']' + + (text || selected || url) + + '[/email]' + ); + } + ); + } + }, + link: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('link')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '[url=' + url + ']' + + (text || selected || url) + + '[/url]' + ); + } + ); + } + }, + quote: { + txtExec: ['[quote]', '[/quote]'] + }, + youtube: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('youtube')._dropDown( + editor, + caller, + function (id) { + editor.insertText('[youtube]' + id + '[/youtube]'); + } + ); + } + }, + rtl: { + txtExec: ['[rtl]', '[/rtl]'] + }, + ltr: { + txtExec: ['[ltr]', '[/ltr]'] + } + }; + + var bbcodeHandlers = { + // START_COMMAND: Bold + b: { + tags: { + b: null, + strong: null + }, + styles: { + // 401 is for FF 3.5 + 'font-weight': ['bold', 'bolder', '401', '700', '800', '900'] + }, + format: '[b]{0}[/b]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Italic + i: { + tags: { + i: null, + em: null + }, + styles: { + 'font-style': ['italic', 'oblique'] + }, + format: '[i]{0}[/i]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Underline + u: { + tags: { + u: null + }, + styles: { + 'text-decoration': ['underline'] + }, + format: '[u]{0}[/u]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Strikethrough + s: { + tags: { + s: null, + strike: null + }, + styles: { + 'text-decoration': ['line-through'] + }, + format: '[s]{0}[/s]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Subscript + sub: { + tags: { + sub: null + }, + format: '[sub]{0}[/sub]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Superscript + sup: { + tags: { + sup: null + }, + format: '[sup]{0}[/sup]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Font + font: { + tags: { + font: { + face: null + } + }, + styles: { + 'font-family': null + }, + quoteType: QuoteType.never, + format: function (element, content) { + var font; + + if (!is(element, 'font') || !(font = attr(element, 'face'))) { + font = css(element, 'font-family'); + } + + return '[font=' + _stripQuotes(font) + ']' + + content + '[/font]'; + }, + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Size + size: { + tags: { + font: { + size: null + } + }, + styles: { + 'font-size': null + }, + format: function (element, content) { + var fontSize = attr(element, 'size'), + size = 2; + + if (!fontSize) { + fontSize = css(element, 'fontSize'); + } + + // Most browsers return px value but IE returns 1-7 + if (fontSize.indexOf('px') > -1) { + // convert size to an int + fontSize = fontSize.replace('px', '') - 0; + + if (fontSize < 12) { + size = 1; + } + if (fontSize > 15) { + size = 3; + } + if (fontSize > 17) { + size = 4; + } + if (fontSize > 23) { + size = 5; + } + if (fontSize > 31) { + size = 6; + } + if (fontSize > 47) { + size = 7; + } + } else { + size = fontSize; + } + + return '[size=' + size + ']' + content + '[/size]'; + }, + html: '{!0}' + }, + // END_COMMAND + + // START_COMMAND: Color + color: { + tags: { + font: { + color: null + } + }, + styles: { + color: null + }, + quoteType: QuoteType.never, + format: function (elm, content) { + var color; + + if (!is(elm, 'font') || !(color = attr(elm, 'color'))) { + color = elm.style.color || css(elm, 'color'); + } + + return '[color=' + _normaliseColour(color) + ']' + + content + '[/color]'; + }, + html: function (token, attrs, content) { + return '' + content + ''; + } + }, + // END_COMMAND + + // START_COMMAND: Lists + ul: { + tags: { + ul: null + }, + breakStart: true, + isInline: false, + skipLastLineBreak: true, + format: '[ul]{0}[/ul]', + html: '
      {0}
    ' + }, + list: { + breakStart: true, + isInline: false, + skipLastLineBreak: true, + html: '
      {0}
    ' + }, + ol: { + tags: { + ol: null + }, + breakStart: true, + isInline: false, + skipLastLineBreak: true, + format: '[ol]{0}[/ol]', + html: '
      {0}
    ' + }, + li: { + tags: { + li: null + }, + isInline: false, + closedBy: ['/ul', '/ol', '/list', '*', 'li'], + format: '[li]{0}[/li]', + html: '
  • {0}
  • ' + }, + '*': { + isInline: false, + closedBy: ['/ul', '/ol', '/list', '*', 'li'], + html: '
  • {0}
  • ' + }, + // END_COMMAND + + // START_COMMAND: Table + table: { + tags: { + table: null + }, + isInline: false, + isHtmlInline: true, + skipLastLineBreak: true, + format: '[table]{0}[/table]', + html: '{0}
    ' + }, + tr: { + tags: { + tr: null + }, + isInline: false, + skipLastLineBreak: true, + format: '[tr]{0}[/tr]', + html: '{0}' + }, + th: { + tags: { + th: null + }, + allowsEmpty: true, + isInline: false, + format: '[th]{0}[/th]', + html: '{0}' + }, + td: { + tags: { + td: null + }, + allowsEmpty: true, + isInline: false, + format: '[td]{0}[/td]', + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Emoticons + emoticon: { + allowsEmpty: true, + tags: { + img: { + src: null, + 'data-sceditor-emoticon': null + } + }, + format: function (element, content) { + return attr(element, EMOTICON_DATA_ATTR) + content; + }, + html: '{0}' + }, + // END_COMMAND + + // START_COMMAND: Horizontal Rule + hr: { + tags: { + hr: null + }, + allowsEmpty: true, + isSelfClosing: true, + isInline: false, + format: '[hr]{0}', + html: '
    ' + }, + // END_COMMAND + + // START_COMMAND: Image + img: { + allowsEmpty: true, + tags: { + img: { + src: null + } + }, + allowedChildren: ['#'], + quoteType: QuoteType.never, + format: function (element, content) { + var width, height, + attribs = '', + style = function (name) { + return element.style ? element.style[name] : null; + }; + + // check if this is an emoticon image + if (attr(element, EMOTICON_DATA_ATTR)) { + return content; + } + + width = attr(element, 'width') || style('width'); + height = attr(element, 'height') || style('height'); + + // only add width and height if one is specified + if ((element.complete && (width || height)) || + (width && height)) { + + attribs = '=' + dom.width(element) + 'x' + + dom.height(element); + } + + return '[img' + attribs + ']' + attr(element, 'src') + '[/img]'; + }, + html: function (token, attrs, content) { + var undef, width, height, match, + attribs = ''; + + // handle [img width=340 height=240]url[/img] + width = attrs.width; + height = attrs.height; + + // handle [img=340x240]url[/img] + if (attrs.defaultattr) { + match = attrs.defaultattr.split(/x/i); + + width = match[0]; + height = (match.length === 2 ? match[1] : match[0]); + } + + if (width !== undef) { + attribs += ' width="' + escapeEntities(width, true) + '"'; + } + + if (height !== undef) { + attribs += ' height="' + escapeEntities(height, true) + '"'; + } + + return ''; + } + }, + // END_COMMAND + + // START_COMMAND: URL + url: { + allowsEmpty: true, + tags: { + a: { + href: null + } + }, + quoteType: QuoteType.never, + format: function (element, content) { + var url = attr(element, 'href'); + + // make sure this link is not an e-mail, + // if it is return e-mail BBCode + if (url.substr(0, 7) === 'mailto:') { + return '[email="' + url.substr(7) + '"]' + + content + '[/email]'; + } + + return '[url=' + url + ']' + content + '[/url]'; + }, + html: function (token, attrs, content) { + attrs.defaultattr = + escapeEntities(attrs.defaultattr, true) || content; + + return '' + + content + ''; + } + }, + // END_COMMAND + + // START_COMMAND: E-mail + email: { + quoteType: QuoteType.never, + html: function (token, attrs, content) { + return '' + content + ''; + } + }, + // END_COMMAND + + // START_COMMAND: Quote + quote: { + tags: { + blockquote: null + }, + isInline: false, + quoteType: QuoteType.never, + format: function (element, content) { + var authorAttr = 'data-author'; + var author = ''; + var cite; + var children = element.children; + + for (var i = 0; !cite && i < children.length; i++) { + if (is(children[i], 'cite')) { + cite = children[i]; + } + } + + if (cite || attr(element, authorAttr)) { + author = cite && cite.textContent || + attr(element, authorAttr); + + attr(element, authorAttr, author); + + if (cite) { + element.removeChild(cite); + } + + content = this.elementToBbcode(element); + author = '=' + author.replace(/(^\s+|\s+$)/g, ''); + + if (cite) { + element.insertBefore(cite, element.firstChild); + } + } + + return '[quote' + author + ']' + content + '[/quote]'; + }, + html: function (token, attrs, content) { + if (attrs.defaultattr) { + content = '' + escapeEntities(attrs.defaultattr) + + '' + content; + } + + return '
    ' + content + '
    '; + } + }, + // END_COMMAND + + // START_COMMAND: Code + code: { + tags: { + code: null + }, + isInline: false, + allowedChildren: ['#', '#newline'], + format: '[code]{0}[/code]', + html: '{0}' + }, + // END_COMMAND + + + // START_COMMAND: Left + left: { + styles: { + 'text-align': [ + 'left', + '-webkit-left', + '-moz-left', + '-khtml-left' + ] + }, + isInline: false, + format: '[left]{0}[/left]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Centre + center: { + styles: { + 'text-align': [ + 'center', + '-webkit-center', + '-moz-center', + '-khtml-center' + ] + }, + isInline: false, + format: '[center]{0}[/center]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Right + right: { + styles: { + 'text-align': [ + 'right', + '-webkit-right', + '-moz-right', + '-khtml-right' + ] + }, + isInline: false, + format: '[right]{0}[/right]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Justify + justify: { + styles: { + 'text-align': [ + 'justify', + '-webkit-justify', + '-moz-justify', + '-khtml-justify' + ] + }, + isInline: false, + format: '[justify]{0}[/justify]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: YouTube + youtube: { + allowsEmpty: true, + tags: { + iframe: { + 'data-youtube-id': null + } + }, + format: function (element, content) { + element = attr(element, 'data-youtube-id'); + + return element ? '[youtube]' + element + '[/youtube]' : content; + }, + html: '' + }, + // END_COMMAND + + + // START_COMMAND: Rtl + rtl: { + styles: { + direction: ['rtl'] + }, + isInline: false, + format: '[rtl]{0}[/rtl]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // START_COMMAND: Ltr + ltr: { + styles: { + direction: ['ltr'] + }, + isInline: false, + format: '[ltr]{0}[/ltr]', + html: '
    {0}
    ' + }, + // END_COMMAND + + // this is here so that commands above can be removed + // without having to remove the , after the last one. + // Needed for IE. + ignore: {} + }; + + /** + * Formats a string replacing {name} with the values of + * obj.name properties. + * + * If there is no property for the specified {name} then + * it will be left intact. + * + * @param {string} str + * @param {Object} obj + * @return {string} + * @since 2.0.0 + */ + function formatBBCodeString(str, obj) { + return str.replace(/\{([^}]+)\}/g, function (match, group) { + var undef, + escape = true; + + if (group.charAt(0) === '!') { + escape = false; + group = group.substring(1); + } + + if (group === '0') { + escape = false; + } + + if (obj[group] === undef) { + return match; + } + + return escape ? escapeEntities(obj[group], true) : obj[group]; + }); + } + + /** + * Removes the first and last divs from the HTML. + * + * This is needed for pasting + * @param {string} html + * @return {string} + * @private + */ + function removeFirstLastDiv(html) { + var node, next, removeDiv, + output = document.createElement('div'); + + removeDiv = function (node, isFirst) { + // Don't remove divs that have styling + if (dom.hasStyling(node)) { + return; + } + + if (IE_BR_FIX || (node.childNodes.length !== 1 || + !is(node.firstChild, 'br'))) { + while ((next = node.firstChild)) { + output.insertBefore(next, node); + } + } + + if (isFirst) { + var lastChild = output.lastChild; + + if (node !== lastChild && is(lastChild, 'div') && + node.nextSibling === lastChild) { + output.insertBefore(document.createElement('br'), node); + } + } + + output.removeChild(node); + }; + + css(output, 'display', 'none'); + output.innerHTML = html.replace(/<\/div>\n/g, ''); + + if ((node = output.firstChild) && is(node, 'div')) { + removeDiv(node, true); + } + + if ((node = output.lastChild) && is(node, 'div')) { + removeDiv(node); + } + + return output.innerHTML; + } + + function isFunction(fn) { + return typeof fn === 'function'; + } + + /** + * Removes any leading or trailing quotes ('") + * + * @return string + * @since v1.4.0 + */ + function _stripQuotes(str) { + return str ? + str.replace(/\\(.)/g, '$1').replace(/^(["'])(.*?)\1$/, '$2') : str; + } + + /** + * Formats a string replacing {0}, {1}, {2}, ect. with + * the params provided + * + * @param {string} str The string to format + * @param {...string} arg The strings to replace + * @return {string} + * @since v1.4.0 + */ + function _formatString(str) { + var undef; + var args = arguments; + + return str.replace(/\{(\d+)\}/g, function (_, matchNum) { + return args[matchNum - 0 + 1] !== undef ? + args[matchNum - 0 + 1] : + '{' + matchNum + '}'; + }); + } + + var TOKEN_OPEN = 'open'; + var TOKEN_CONTENT = 'content'; + var TOKEN_NEWLINE = 'newline'; + var TOKEN_CLOSE = 'close'; + + + /* + * @typedef {Object} TokenizeToken + * @property {string} type + * @property {string} name + * @property {string} val + * @property {Object.} attrs + * @property {array} children + * @property {TokenizeToken} closing + */ + + /** + * Tokenize token object + * + * @param {string} type The type of token this is, + * should be one of tokenType + * @param {string} name The name of this token + * @param {string} val The originally matched string + * @param {array} attrs Any attributes. Only set on + * TOKEN_TYPE_OPEN tokens + * @param {array} children Any children of this token + * @param {TokenizeToken} closing This tokens closing tag. + * Only set on TOKEN_TYPE_OPEN tokens + * @class {TokenizeToken} + * @name {TokenizeToken} + * @memberOf BBCodeParser.prototype + */ + // eslint-disable-next-line max-params + function TokenizeToken(type, name, val, attrs, children, closing) { + var base = this; + + base.type = type; + base.name = name; + base.val = val; + base.attrs = attrs || {}; + base.children = children || []; + base.closing = closing || null; + }; + + TokenizeToken.prototype = { + /** @lends BBCodeParser.prototype.TokenizeToken */ + /** + * Clones this token + * + * @return {TokenizeToken} + */ + clone: function () { + var base = this; + + return new TokenizeToken( + base.type, + base.name, + base.val, + extend({}, base.attrs), + [], + base.closing ? base.closing.clone() : null + ); + }, + /** + * Splits this token at the specified child + * + * @param {TokenizeToken} splitAt The child to split at + * @return {TokenizeToken} The right half of the split token or + * empty clone if invalid splitAt lcoation + */ + splitAt: function (splitAt) { + var offsetLength; + var base = this; + var clone = base.clone(); + var offset = base.children.indexOf(splitAt); + + if (offset > -1) { + // Work out how many items are on the right side of the split + // to pass to splice() + offsetLength = base.children.length - offset; + clone.children = base.children.splice(offset, offsetLength); + } + + return clone; + } + }; + + + /** + * SCEditor BBCode parser class + * + * @param {Object} options + * @class BBCodeParser + * @name BBCodeParser + * @since v1.4.0 + */ + function BBCodeParser(options) { + var base = this; + + base.opts = extend({}, BBCodeParser.defaults, options); + + /** + * Takes a BBCode string and splits it into open, + * content and close tags. + * + * It does no checking to verify a tag has a matching open + * or closing tag or if the tag is valid child of any tag + * before it. For that the tokens should be passed to the + * parse function. + * + * @param {string} str + * @return {array} + * @memberOf BBCodeParser.prototype + */ + base.tokenize = function (str) { + var matches, type, i; + var tokens = []; + // The token types in reverse order of precedence + // (they're looped in reverse) + var tokenTypes = [ + { + type: TOKEN_CONTENT, + regex: /^([^\[\r\n]+|\[)/ + }, + { + type: TOKEN_NEWLINE, + regex: /^(\r\n|\r|\n)/ + }, + { + type: TOKEN_OPEN, + regex: /^\[[^\[\]]+\]/ + }, + // Close must come before open as they are + // the same except close has a / at the start. + { + type: TOKEN_CLOSE, + regex: /^\[\/[^\[\]]+\]/ + } + ]; + + strloop: + while (str.length) { + i = tokenTypes.length; + while (i--) { + type = tokenTypes[i].type; + + // Check if the string matches any of the tokens + if (!(matches = str.match(tokenTypes[i].regex)) || + !matches[0]) { + continue; + } + + // Add the match to the tokens list + tokens.push(tokenizeTag(type, matches[0])); + + // Remove the match from the string + str = str.substr(matches[0].length); + + // The token has been added so start again + continue strloop; + } + + // If there is anything left in the string which doesn't match + // any of the tokens then just assume it's content and add it. + if (str.length) { + tokens.push(tokenizeTag(TOKEN_CONTENT, str)); + } + + str = ''; + } + + return tokens; + }; + + /** + * Extracts the name an params from a tag + * + * @param {string} type + * @param {string} val + * @return {Object} + * @private + */ + function tokenizeTag(type, val) { + var matches, attrs, name, + openRegex = /\[([^\]\s=]+)(?:([^\]]+))?\]/, + closeRegex = /\[\/([^\[\]]+)\]/; + + // Extract the name and attributes from opening tags and + // just the name from closing tags. + if (type === TOKEN_OPEN && (matches = val.match(openRegex))) { + name = lower(matches[1]); + + if (matches[2] && (matches[2] = matches[2].trim())) { + attrs = tokenizeAttrs(matches[2]); + } + } + + if (type === TOKEN_CLOSE && + (matches = val.match(closeRegex))) { + name = lower(matches[1]); + } + + if (type === TOKEN_NEWLINE) { + name = '#newline'; + } + + // Treat all tokens without a name and + // all unknown BBCodes as content + if (!name || ((type === TOKEN_OPEN || type === TOKEN_CLOSE) && + !bbcodeHandlers[name])) { + + type = TOKEN_CONTENT; + name = '#'; + } + + return new TokenizeToken(type, name, val, attrs); + } + + /** + * Extracts the individual attributes from a string containing + * all the attributes. + * + * @param {string} attrs + * @return {Object} Assoc array of attributes + * @private + */ + function tokenizeAttrs(attrs) { + var matches, + /* + ([^\s=]+) Anything that's not a space or equals + = Equals sign = + (?: + (?: + (["']) The opening quote + ( + (?:\\\2|[^\2])*? Anything that isn't the + unescaped opening quote + ) + \2 The opening quote again which + will close the string + ) + | If not a quoted string then match + ( + (?:.(?!\s\S+=))*.? Anything that isn't part of + [space][non-space][=] which + would be a new attribute + ) + ) + */ + attrRegex = /([^\s=]+)=(?:(?:(["'])((?:\\\2|[^\2])*?)\2)|((?:.(?!\s\S+=))*.))/g, + ret = {}; + + // if only one attribute then remove the = from the start and + // strip any quotes + if (attrs.charAt(0) === '=' && attrs.indexOf('=', 1) < 0) { + ret.defaultattr = _stripQuotes(attrs.substr(1)); + } else { + if (attrs.charAt(0) === '=') { + attrs = 'defaultattr' + attrs; + } + + // No need to strip quotes here, the regex will do that. + while ((matches = attrRegex.exec(attrs))) { + ret[lower(matches[1])] = + _stripQuotes(matches[3]) || matches[4]; + } + } + + return ret; + } + + /** + * Parses a string into an array of BBCodes + * + * @param {string} str + * @param {boolean} preserveNewLines If to preserve all new lines, not + * strip any based on the passed + * formatting options + * @return {array} Array of BBCode objects + * @memberOf BBCodeParser.prototype + */ + base.parse = function (str, preserveNewLines) { + var ret = parseTokens(base.tokenize(str)); + var opts = base.opts; + + if (opts.fixInvalidNesting) { + fixNesting(ret); + } + + normaliseNewLines(ret, null, preserveNewLines); + + if (opts.removeEmptyTags) { + removeEmpty(ret); + } + + return ret; + }; + + /** + * Checks if an array of TokenizeToken's contains the + * specified token. + * + * Checks the tokens name and type match another tokens + * name and type in the array. + * + * @param {string} name + * @param {string} type + * @param {array} arr + * @return {Boolean} + * @private + */ + function hasTag(name, type, arr) { + var i = arr.length; + + while (i--) { + if (arr[i].type === type && arr[i].name === name) { + return true; + } + } + + return false; + } + + /** + * Checks if the child tag is allowed as one + * of the parent tags children. + * + * @param {TokenizeToken} parent + * @param {TokenizeToken} child + * @return {Boolean} + * @private + */ + function isChildAllowed(parent, child) { + var parentBBCode = parent ? bbcodeHandlers[parent.name] : {}, + allowedChildren = parentBBCode.allowedChildren; + + if (base.opts.fixInvalidChildren && allowedChildren) { + return allowedChildren.indexOf(child.name || '#') > -1; + } + + return true; + } + + // TODO: Tidy this parseTokens() function up a bit. + /** + * Parses an array of tokens created by tokenize() + * + * @param {array} toks + * @return {array} Parsed tokens + * @see tokenize() + * @private + */ + function parseTokens(toks) { + var token, bbcode, curTok, clone, i, next, + cloned = [], + output = [], + openTags = [], + /** + * Returns the currently open tag or undefined + * @return {TokenizeToken} + */ + currentTag = function () { + return last(openTags); + }, + /** + * Adds a tag to either the current tags children + * or to the output array. + * @param {TokenizeToken} token + * @private + */ + addTag = function (token) { + if (currentTag()) { + currentTag().children.push(token); + } else { + output.push(token); + } + }, + /** + * Checks if this tag closes the current tag + * @param {string} name + * @return {Void} + */ + closesCurrentTag = function (name) { + return currentTag() && + (bbcode = bbcodeHandlers[currentTag().name]) && + bbcode.closedBy && + bbcode.closedBy.indexOf(name) > -1; + }; + + while ((token = toks.shift())) { + next = toks[0]; + + /* + * Fixes any invalid children. + * + * If it is an element which isn't allowed as a child of it's + * parent then it will be converted to content of the parent + * element. i.e. + * [code]Code [b]only[/b] allows text.[/code] + * Will become: + * Code [b]only[/b] allows text. + * Instead of: + * Code only allows text. + */ + // Ignore tags that can't be children + if (!isChildAllowed(currentTag(), token)) { + + // exclude closing tags of current tag + if (token.type !== TOKEN_CLOSE || !currentTag() || + token.name !== currentTag().name) { + token.name = '#'; + token.type = TOKEN_CONTENT; + } + } + + switch (token.type) { + case TOKEN_OPEN: + // Check it this closes a parent, + // e.g. for lists [*]one [*]two + if (closesCurrentTag(token.name)) { + openTags.pop(); + } + + addTag(token); + bbcode = bbcodeHandlers[token.name]; + + // If this tag is not self closing and it has a closing + // tag then it is open and has children so add it to the + // list of open tags. If has the closedBy property then + // it is closed by other tags so include everything as + // it's children until one of those tags is reached. + if (bbcode && !bbcode.isSelfClosing && + (bbcode.closedBy || + hasTag(token.name, TOKEN_CLOSE, toks))) { + openTags.push(token); + } else if (!bbcode || !bbcode.isSelfClosing) { + token.type = TOKEN_CONTENT; + } + break; + + case TOKEN_CLOSE: + // check if this closes the current tag, + // e.g. [/list] would close an open [*] + if (currentTag() && token.name !== currentTag().name && + closesCurrentTag('/' + token.name)) { + + openTags.pop(); + } + + // If this is closing the currently open tag just pop + // the close tag off the open tags array + if (currentTag() && token.name === currentTag().name) { + currentTag().closing = token; + openTags.pop(); + + // If this is closing an open tag that is the parent of + // the current tag then clone all the tags including the + // current one until reaching the parent that is being + // closed. Close the parent and then add the clones back + // in. + } else if (hasTag(token.name, TOKEN_OPEN, openTags)) { + + // Remove the tag from the open tags + while ((curTok = openTags.pop())) { + + // If it's the tag that is being closed then + // discard it and break the loop. + if (curTok.name === token.name) { + curTok.closing = token; + break; + } + + // Otherwise clone this tag and then add any + // previously cloned tags as it's children + clone = curTok.clone(); + + if (cloned.length) { + clone.children.push(last(cloned)); + } + + cloned.push(clone); + } + + // Place block linebreak before cloned tags + if (next && next.type === TOKEN_NEWLINE) { + bbcode = bbcodeHandlers[token.name]; + if (bbcode && bbcode.isInline === false) { + addTag(next); + toks.shift(); + } + } + + // Add the last cloned child to the now current tag + // (the parent of the tag which was being closed) + addTag(last(cloned)); + + // Add all the cloned tags to the open tags list + i = cloned.length; + while (i--) { + openTags.push(cloned[i]); + } + + cloned.length = 0; + + // This tag is closing nothing so treat it as content + } else { + token.type = TOKEN_CONTENT; + addTag(token); + } + break; + + case TOKEN_NEWLINE: + // handle things like + // [*]list\nitem\n[*]list1 + // where it should come out as + // [*]list\nitem[/*]\n[*]list1[/*] + // instead of + // [*]list\nitem\n[/*][*]list1[/*] + if (currentTag() && next && closesCurrentTag( + (next.type === TOKEN_CLOSE ? '/' : '') + + next.name + )) { + // skip if the next tag is the closing tag for + // the option tag, i.e. [/*] + if (!(next.type === TOKEN_CLOSE && + next.name === currentTag().name)) { + bbcode = bbcodeHandlers[currentTag().name]; + + if (bbcode && bbcode.breakAfter) { + openTags.pop(); + } else if (bbcode && + bbcode.isInline === false && + base.opts.breakAfterBlock && + bbcode.breakAfter !== false) { + openTags.pop(); + } + } + } + + addTag(token); + break; + + default: // content + addTag(token); + break; + } + } + + return output; + } + + /** + * Normalise all new lines + * + * Removes any formatting new lines from the BBCode + * leaving only content ones. I.e. for a list: + * + * [list] + * [*] list item one + * with a line break + * [*] list item two + * [/list] + * + * would become + * + * [list] [*] list item one + * with a line break [*] list item two [/list] + * + * Which makes it easier to convert to HTML or add + * the formatting new lines back in when converting + * back to BBCode + * + * @param {array} children + * @param {TokenizeToken} parent + * @param {boolean} onlyRemoveBreakAfter + * @return {void} + */ + function normaliseNewLines(children, parent, onlyRemoveBreakAfter) { + var token, left, right, parentBBCode, bbcode, + removedBreakEnd, removedBreakBefore, remove; + var childrenLength = children.length; + // TODO: this function really needs tidying up + if (parent) { + parentBBCode = bbcodeHandlers[parent.name]; + } + + var i = childrenLength; + while (i--) { + if (!(token = children[i])) { + continue; + } + + if (token.type === TOKEN_NEWLINE) { + left = i > 0 ? children[i - 1] : null; + right = i < childrenLength - 1 ? children[i + 1] : null; + remove = false; + + // Handle the start and end new lines + // e.g. [tag]\n and \n[/tag] + if (!onlyRemoveBreakAfter && parentBBCode && + parentBBCode.isSelfClosing !== true) { + // First child of parent so must be opening line break + // (breakStartBlock, breakStart) e.g. [tag]\n + if (!left) { + if (parentBBCode.isInline === false && + base.opts.breakStartBlock && + parentBBCode.breakStart !== false) { + remove = true; + } + + if (parentBBCode.breakStart) { + remove = true; + } + // Last child of parent so must be end line break + // (breakEndBlock, breakEnd) + // e.g. \n[/tag] + // remove last line break (breakEndBlock, breakEnd) + } else if (!removedBreakEnd && !right) { + if (parentBBCode.isInline === false && + base.opts.breakEndBlock && + parentBBCode.breakEnd !== false) { + remove = true; + } + + if (parentBBCode.breakEnd) { + remove = true; + } + + removedBreakEnd = remove; + } + } + + if (left && left.type === TOKEN_OPEN) { + if ((bbcode = bbcodeHandlers[left.name])) { + if (!onlyRemoveBreakAfter) { + if (bbcode.isInline === false && + base.opts.breakAfterBlock && + bbcode.breakAfter !== false) { + remove = true; + } + + if (bbcode.breakAfter) { + remove = true; + } + } else if (bbcode.isInline === false) { + remove = true; + } + } + } + + if (!onlyRemoveBreakAfter && !removedBreakBefore && + right && right.type === TOKEN_OPEN) { + + if ((bbcode = bbcodeHandlers[right.name])) { + if (bbcode.isInline === false && + base.opts.breakBeforeBlock && + bbcode.breakBefore !== false) { + remove = true; + } + + if (bbcode.breakBefore) { + remove = true; + } + + removedBreakBefore = remove; + + if (remove) { + children.splice(i, 1); + continue; + } + } + } + + if (remove) { + children.splice(i, 1); + } + + // reset double removedBreakBefore removal protection. + // This is needed for cases like \n\n[\tag] where + // only 1 \n should be removed but without this they both + // would be. + removedBreakBefore = false; + } else if (token.type === TOKEN_OPEN) { + normaliseNewLines(token.children, token, + onlyRemoveBreakAfter); + } + } + } + + /** + * Fixes any invalid nesting. + * + * If it is a block level element inside 1 or more inline elements + * then those inline elements will be split at the point where the + * block level is and the block level element placed between the split + * parts. i.e. + * [inline]A[blocklevel]B[/blocklevel]C[/inline] + * Will become: + * [inline]A[/inline][blocklevel]B[/blocklevel][inline]C[/inline] + * + * @param {array} children + * @param {array} [parents] Null if there is no parents + * @param {boolea} [insideInline] If inside an inline element + * @param {array} [rootArr] Root array if there is one + * @return {array} + * @private + */ + function fixNesting(children, parents, insideInline, rootArr) { + var token, i, parent, parentIndex, parentParentChildren, right; + + var isInline = function (token) { + var bbcode = bbcodeHandlers[token.name]; + + return !bbcode || bbcode.isInline !== false; + }; + + parents = parents || []; + rootArr = rootArr || children; + + // This must check the length each time as it can change when + // tokens are moved to fix the nesting. + for (i = 0; i < children.length; i++) { + if (!(token = children[i]) || token.type !== TOKEN_OPEN) { + continue; + } + + if (insideInline && !isInline(token)) { + // if this is a blocklevel element inside an inline one then + // split the parent at the block level element + parent = last(parents); + right = parent.splitAt(token); + + parentParentChildren = parents.length > 1 ? + parents[parents.length - 2].children : rootArr; + + // If parent inline is allowed inside this tag, clone it and + // wrap this tags children in it. + if (isChildAllowed(token, parent)) { + var clone = parent.clone(); + clone.children = token.children; + token.children = [clone]; + } + + parentIndex = parentParentChildren.indexOf(parent); + if (parentIndex > -1) { + // remove the block level token from the right side of + // the split inline element + right.children.splice(0, 1); + + // insert the block level token and the right side after + // the left side of the inline token + parentParentChildren.splice( + parentIndex + 1, 0, token, right + ); + + // If token is a block and is followed by a newline, + // then move the newline along with it to the new parent + var next = right.children[0]; + if (next && next.type === TOKEN_NEWLINE) { + if (!isInline(token)) { + right.children.splice(0, 1); + parentParentChildren.splice( + parentIndex + 2, 0, next + ); + } + } + + // return to parents loop as the + // children have now increased + return; + } + + } + + parents.push(token); + + fixNesting( + token.children, + parents, + insideInline || isInline(token), + rootArr + ); + + parents.pop(); + } + } + + /** + * Removes any empty BBCodes which are not allowed to be empty. + * + * @param {array} tokens + * @private + */ + function removeEmpty(tokens) { + var token, bbcode; + + /** + * Checks if all children are whitespace or not + * @private + */ + var isTokenWhiteSpace = function (children) { + var j = children.length; + + while (j--) { + var type = children[j].type; + + if (type === TOKEN_OPEN || type === TOKEN_CLOSE) { + return false; + } + + if (type === TOKEN_CONTENT && + /\S|\u00A0/.test(children[j].val)) { + return false; + } + } + + return true; + }; + + var i = tokens.length; + while (i--) { + // So skip anything that isn't a tag since only tags can be + // empty, content can't + if (!(token = tokens[i]) || token.type !== TOKEN_OPEN) { + continue; + } + + bbcode = bbcodeHandlers[token.name]; + + // Remove any empty children of this tag first so that if they + // are all removed this one doesn't think it's not empty. + removeEmpty(token.children); + + if (isTokenWhiteSpace(token.children) && bbcode && + !bbcode.isSelfClosing && !bbcode.allowsEmpty) { + tokens.splice.apply(tokens, [i, 1].concat(token.children)); + } + } + } + + /** + * Converts a BBCode string to HTML + * + * @param {string} str + * @param {boolean} preserveNewLines If to preserve all new lines, not + * strip any based on the passed + * formatting options + * @return {string} + * @memberOf BBCodeParser.prototype + */ + base.toHTML = function (str, preserveNewLines) { + return convertToHTML(base.parse(str, preserveNewLines), true); + }; + + /** + * @private + */ + function convertToHTML(tokens, isRoot) { + var undef, token, bbcode, content, html, needsBlockWrap, + blockWrapOpen, isInline, lastChild, + ret = []; + + isInline = function (bbcode) { + return (!bbcode || (bbcode.isHtmlInline !== undef ? + bbcode.isHtmlInline : bbcode.isInline)) !== false; + }; + + while (tokens.length > 0) { + if (!(token = tokens.shift())) { + continue; + } + + if (token.type === TOKEN_OPEN) { + lastChild = token.children[token.children.length - 1] || {}; + bbcode = bbcodeHandlers[token.name]; + needsBlockWrap = isRoot && isInline(bbcode); + content = convertToHTML(token.children, false); + + if (bbcode && bbcode.html) { + // Only add a line break to the end if this is + // blocklevel and the last child wasn't block-level + if (!isInline(bbcode) && + isInline(bbcodeHandlers[lastChild.name]) && + !bbcode.isPreFormatted && + !bbcode.skipLastLineBreak) { + // Add placeholder br to end of block level elements + // in all browsers apart from IE < 9 which handle + // new lines differently and doesn't need one. + if (!IE_BR_FIX) { + content += '
    '; + } + } + + if (!isFunction(bbcode.html)) { + token.attrs['0'] = content; + html = formatBBCodeString( + bbcode.html, + token.attrs + ); + } else { + html = bbcode.html.call( + base, + token, + token.attrs, + content + ); + } + } else { + html = token.val + content + + (token.closing ? token.closing.val : ''); + } + } else if (token.type === TOKEN_NEWLINE) { + if (!isRoot) { + ret.push('
    '); + continue; + } + + // If not already in a block wrap then start a new block + if (!blockWrapOpen) { + ret.push('
    '); + } + + // Putting BR in a div in IE causes it + // to do a double line break. + if (!IE_BR_FIX) { + ret.push('
    '); + } + + // Normally the div acts as a line-break with by moving + // whatever comes after onto a new line. + // If this is the last token, add an extra line-break so it + // shows as there will be nothing after it. + if (!tokens.length) { + ret.push('
    '); + } + + ret.push('
    \n'); + blockWrapOpen = false; + continue; + // content + } else { + needsBlockWrap = isRoot; + html = escapeEntities(token.val, true); + } + + if (needsBlockWrap && !blockWrapOpen) { + ret.push('
    '); + blockWrapOpen = true; + } else if (!needsBlockWrap && blockWrapOpen) { + ret.push('
    \n'); + blockWrapOpen = false; + } + + ret.push(html); + } + + if (blockWrapOpen) { + ret.push('\n'); + } + + return ret.join(''); + } + + /** + * Takes a BBCode string, parses it then converts it back to BBCode. + * + * This will auto fix the BBCode and format it with the specified + * options. + * + * @param {string} str + * @param {boolean} preserveNewLines If to preserve all new lines, not + * strip any based on the passed + * formatting options + * @return {string} + * @memberOf BBCodeParser.prototype + */ + base.toBBCode = function (str, preserveNewLines) { + return convertToBBCode(base.parse(str, preserveNewLines)); + }; + + /** + * Converts parsed tokens back into BBCode with the + * formatting specified in the options and with any + * fixes specified. + * + * @param {array} toks Array of parsed tokens from base.parse() + * @return {string} + * @private + */ + function convertToBBCode(toks) { + var token, attr, bbcode, isBlock, isSelfClosing, quoteType, + breakBefore, breakStart, breakEnd, breakAfter, + // Create an array of strings which are joined together + // before being returned as this is faster in slow browsers. + // (Old versions of IE). + ret = []; + + while (toks.length > 0) { + if (!(token = toks.shift())) { + continue; + } + // TODO: tidy this + bbcode = bbcodeHandlers[token.name]; + isBlock = !(!bbcode || bbcode.isInline !== false); + isSelfClosing = bbcode && bbcode.isSelfClosing; + + breakBefore = (isBlock && base.opts.breakBeforeBlock && + bbcode.breakBefore !== false) || + (bbcode && bbcode.breakBefore); + + breakStart = (isBlock && !isSelfClosing && + base.opts.breakStartBlock && + bbcode.breakStart !== false) || + (bbcode && bbcode.breakStart); + + breakEnd = (isBlock && base.opts.breakEndBlock && + bbcode.breakEnd !== false) || + (bbcode && bbcode.breakEnd); + + breakAfter = (isBlock && base.opts.breakAfterBlock && + bbcode.breakAfter !== false) || + (bbcode && bbcode.breakAfter); + + quoteType = (bbcode ? bbcode.quoteType : null) || + base.opts.quoteType || QuoteType.auto; + + if (!bbcode && token.type === TOKEN_OPEN) { + ret.push(token.val); + + if (token.children) { + ret.push(convertToBBCode(token.children)); + } + + if (token.closing) { + ret.push(token.closing.val); + } + } else if (token.type === TOKEN_OPEN) { + if (breakBefore) { + ret.push('\n'); + } + + // Convert the tag and it's attributes to BBCode + ret.push('[' + token.name); + if (token.attrs) { + if (token.attrs.defaultattr) { + ret.push('=', quote( + token.attrs.defaultattr, + quoteType, + 'defaultattr' + )); + + delete token.attrs.defaultattr; + } + + for (attr in token.attrs) { + if (token.attrs.hasOwnProperty(attr)) { + ret.push(' ', attr, '=', + quote(token.attrs[attr], quoteType, attr)); + } + } + } + ret.push(']'); + + if (breakStart) { + ret.push('\n'); + } + + // Convert the tags children to BBCode + if (token.children) { + ret.push(convertToBBCode(token.children)); + } + + // add closing tag if not self closing + if (!isSelfClosing && !bbcode.excludeClosing) { + if (breakEnd) { + ret.push('\n'); + } + + ret.push('[/' + token.name + ']'); + } + + if (breakAfter) { + ret.push('\n'); + } + + // preserve whatever was recognized as the + // closing tag if it is a self closing tag + if (token.closing && isSelfClosing) { + ret.push(token.closing.val); + } + } else { + ret.push(token.val); + } + } + + return ret.join(''); + } + + /** + * Quotes an attribute + * + * @param {string} str + * @param {BBCodeParser.QuoteType} quoteType + * @param {string} name + * @return {string} + * @private + */ + function quote(str, quoteType, name) { + var needsQuotes = /\s|=/.test(str); + + if (isFunction(quoteType)) { + return quoteType(str, name); + } + + if (quoteType === QuoteType.never || + (quoteType === QuoteType.auto && !needsQuotes)) { + return str; + } + + return '"' + str.replace('\\', '\\\\').replace('"', '\\"') + '"'; + } + + /** + * Returns the last element of an array or null + * + * @param {array} arr + * @return {Object} Last element + * @private + */ + function last(arr) { + if (arr.length) { + return arr[arr.length - 1]; + } + + return null; + } + + /** + * Converts a string to lowercase. + * + * @param {string} str + * @return {string} Lowercase version of str + * @private + */ + function lower(str) { + return str.toLowerCase(); + } + }; + + /** + * Quote type + * @type {Object} + * @class QuoteType + * @name BBCodeParser.QuoteType + * @since v1.4.0 + */ + BBCodeParser.QuoteType = QuoteType; + + /** + * Default BBCode parser options + * @type {Object} + */ + BBCodeParser.defaults = { + /** + * If to add a new line before block level elements + * + * @type {Boolean} + */ + breakBeforeBlock: false, + + /** + * If to add a new line after the start of block level elements + * + * @type {Boolean} + */ + breakStartBlock: false, + + /** + * If to add a new line before the end of block level elements + * + * @type {Boolean} + */ + breakEndBlock: false, + + /** + * If to add a new line after block level elements + * + * @type {Boolean} + */ + breakAfterBlock: true, + + /** + * If to remove empty tags + * + * @type {Boolean} + */ + removeEmptyTags: true, + + /** + * If to fix invalid nesting, + * i.e. block level elements inside inline elements. + * + * @type {Boolean} + */ + fixInvalidNesting: true, + + /** + * If to fix invalid children. + * i.e. A tag which is inside a parent that doesn't + * allow that type of tag. + * + * @type {Boolean} + */ + fixInvalidChildren: true, + + /** + * Attribute quote type + * + * @type {BBCodeParser.QuoteType} + * @since 1.4.1 + */ + quoteType: QuoteType.auto + }; + + /** + * Converts a number 0-255 to hex. + * + * Will return 00 if number is not a valid number. + * + * @param {any} number + * @return {string} + * @private + */ + function toHex(number) { + number = parseInt(number, 10); + + if (isNaN(number)) { + return '00'; + } + + number = Math.max(0, Math.min(number, 255)).toString(16); + + return number.length < 2 ? '0' + number : number; + } + /** + * Normalises a CSS colour to hex #xxxxxx format + * + * @param {string} colorStr + * @return {string} + * @private + */ + function _normaliseColour(colorStr) { + var match; + + colorStr = colorStr || '#000'; + + // rgb(n,n,n); + if ((match = + colorStr.match(/rgb\((\d{1,3}),\s*?(\d{1,3}),\s*?(\d{1,3})\)/i))) { + return '#' + + toHex(match[1]) + + toHex(match[2]) + + toHex(match[3]); + } + + // expand shorthand + if ((match = colorStr.match(/#([0-f])([0-f])([0-f])\s*?$/i))) { + return '#' + + match[1] + match[1] + + match[2] + match[2] + + match[3] + match[3]; + } + + return colorStr; + } + + /** + * SCEditor BBCode format + * @since 2.0.0 + */ + function bbcodeFormat() { + var base = this; + + base.stripQuotes = _stripQuotes; + + /** + * cache of all the tags pointing to their bbcodes to enable + * faster lookup of which bbcode a tag should have + * @private + */ + var tagsToBBCodes = {}; + + /** + * Same as tagsToBBCodes but instead of HTML tags it's styles + * @private + */ + var stylesToBBCodes = {}; + + /** + * Allowed children of specific HTML tags. Empty array if no + * children other than text nodes are allowed + * @private + */ + var validChildren = { + ul: ['li', 'ol', 'ul'], + ol: ['li', 'ol', 'ul'], + table: ['tr'], + tr: ['td', 'th'], + code: ['br', 'p', 'div'] + }; + + /** + * Populates tagsToBBCodes and stylesToBBCodes to enable faster lookups + * + * @private + */ + function buildBbcodeCache() { + each(bbcodeHandlers, function (bbcode) { + var isBlock, + tags = bbcodeHandlers[bbcode].tags, + styles = bbcodeHandlers[bbcode].styles; + + if (tags) { + each(tags, function (tag, values) { + isBlock = bbcodeHandlers[bbcode].isInline === false; + + tagsToBBCodes[tag] = tagsToBBCodes[tag] || {}; + + tagsToBBCodes[tag][isBlock] = + tagsToBBCodes[tag][isBlock] || {}; + + tagsToBBCodes[tag][isBlock][bbcode] = values; + }); + } + + if (styles) { + each(styles, function (style, values) { + isBlock = bbcodeHandlers[bbcode].isInline === false; + + stylesToBBCodes[isBlock] = + stylesToBBCodes[isBlock] || {}; + + stylesToBBCodes[isBlock][style] = + stylesToBBCodes[isBlock][style] || {}; + + stylesToBBCodes[isBlock][style][bbcode] = values; + }); + } + }); + }; + + /** + * Checks if any bbcode styles match the elements styles + * + * @param {!HTMLElement} element + * @param {string} content + * @param {boolean} [blockLevel=false] + * @return {string} Content with any matching + * bbcode tags wrapped around it. + * @private + */ + function handleStyles(element, content, blockLevel) { + var styleValue, format, + getStyle = dom.getStyle; + + // convert blockLevel to boolean + blockLevel = !!blockLevel; + + if (!stylesToBBCodes[blockLevel]) { + return content; + } + + each(stylesToBBCodes[blockLevel], function (property, bbcodes) { + styleValue = getStyle(element, property); + + // if the parent has the same style use that instead of this one + // so you don't end up with [i]parent[i]child[/i][/i] + if (!styleValue || + getStyle(element.parentNode, property) === styleValue) { + return; + } + + each(bbcodes, function (bbcode, values) { + if (!values || values.indexOf(styleValue.toString()) > -1) { + format = bbcodeHandlers[bbcode].format; + + if (isFunction(format)) { + content = format.call(base, element, content); + } else { + content = _formatString(format, content); + } + } + }); + }); + + return content; + } + + /** + * Handles adding newlines after block level elements + * + * @param {HTMLElement} element The element to convert + * @param {string} content The tags text content + * @return {string} + * @private + */ + function handleBlockNewlines(element, content) { + var tag = element.nodeName.toLowerCase(); + var isInline = dom.isInline; + if (!isInline(element, true) || tag === 'br') { + var isLastBlockChild, parent, parentLastChild, + previousSibling = element.previousSibling; + + // Skips selection makers and ignored elements + // Skip empty inline elements + while (previousSibling && + previousSibling.nodeType === 1 && + !is(previousSibling, 'br') && + isInline(previousSibling, true) && + !previousSibling.firstChild) { + previousSibling = previousSibling.previousSibling; + } + + // If it's the last block of an inline that is the last + // child of a block then it shouldn't cause a line break + // except in IE < 11 + //
    + do { + parent = element.parentNode; + parentLastChild = parent && parent.lastChild; + + isLastBlockChild = parentLastChild === element; + element = parent; + } while (parent && isLastBlockChild && isInline(parent, true)); + + // If this block is: + // * Not the last child of a block level element + // * Is a
  • tag (lists are blocks) + // * Is IE < 11 and the tag is BR. IE < 11 never collapses BR + // tags. + if (!isLastBlockChild || tag === 'li' || + (tag === 'br' && IE_BR_FIX)) { + content += '\n'; + } + + // Check for: + // texttext + // + // The second opening opening tag should cause a + // line break because the previous sibing is inline. + if (tag !== 'br' && previousSibling && + !is(previousSibling, 'br') && + isInline(previousSibling, true)) { + content = '\n' + content; + } + } + + return content; + } + + /** + * Handles a HTML tag and finds any matching bbcodes + * + * @param {HTMLElement} element The element to convert + * @param {string} content The Tags text content + * @param {boolean} [blockLevel=false] If to convert block level tags + * @return {string} Content with any matching bbcode tags + * wrapped around it. + * @private + */ + function handleTags(element, content, blockLevel) { + var convertBBCode, format, + tag = element.nodeName.toLowerCase(); + + // convert blockLevel to boolean + blockLevel = !!blockLevel; + + if (tagsToBBCodes[tag] && tagsToBBCodes[tag][blockLevel]) { + // loop all bbcodes for this tag + each(tagsToBBCodes[tag][blockLevel], function ( + bbcode, bbcodeAttribs) { + // if the bbcode requires any attributes then check this has + // all needed + if (bbcodeAttribs) { + convertBBCode = false; + + // loop all the bbcode attribs + each(bbcodeAttribs, function (attrib, values) { + // Skip if the element doesn't have the attibue or + // the attribute doesn't match one of the require + // values + if (!attr(element, attrib) || (values && + values.indexOf(attr(element, attrib)) < 0)) { + return; + } + + // break this loop as we have matched this bbcode + convertBBCode = true; + return false; + }); + + if (!convertBBCode) { + return; + } + } + + format = bbcodeHandlers[bbcode].format; + + if (isFunction(format)) { + content = format.call(base, element, content); + } else { + content = _formatString(format, content); + } + }); + } + + return content; + } + + /** + * Converts a HTML dom element to BBCode starting from + * the innermost element and working backwards + * + * @private + * @param {HTMLElement} element + * @return {string} BBCode + * @memberOf SCEditor.plugins.bbcode.prototype + */ + function elementToBbcode(element) { + var toBBCode = function (node, vChildren) { + var ret = ''; + + dom.traverse(node, function (node) { + var curTag = '', + nodeType = node.nodeType, + tag = node.nodeName.toLowerCase(), + vChild = validChildren[tag], + firstChild = node.firstChild, + isValidChild = true; + + if (typeof vChildren === 'object') { + isValidChild = vChildren.indexOf(tag) > -1; + + // Emoticons should always be converted + if (is(node, 'img') && attr(node, EMOTICON_DATA_ATTR)) { + isValidChild = true; + } + + // if this tag is one of the parents allowed children + // then set this tags allowed children to whatever it + // allows, otherwise set to what the parent allows + if (!isValidChild) { + vChild = vChildren; + } + } + + // 3 = text and 1 = element + if (nodeType !== 3 && nodeType !== 1) { + return; + } + + if (nodeType === 1) { + // skip empty nlf elements (new lines automatically + // added after block level elements like quotes) + if (is(node, '.sceditor-nlf')) { + if (!firstChild || (!IE_BR_FIX && + node.childNodes.length === 1 && + /br/i.test(firstChild.nodeName))) { + return; + } + } + + // don't convert iframe contents + if (tag !== 'iframe') { + curTag = toBBCode(node, vChild); + } + + // TODO: isValidChild is no longer needed. Should use + // valid children bbcodes instead by creating BBCode + // tokens like the parser. + if (isValidChild) { + // code tags should skip most styles + if (tag !== 'code') { + // handle inline bbcodes + curTag = handleStyles(node, curTag); + curTag = handleTags(node, curTag); + + // handle blocklevel bbcodes + curTag = handleStyles(node, curTag, true); + } + + curTag = handleTags(node, curTag, true); + ret += handleBlockNewlines(node, curTag); + } else { + ret += curTag; + } + } else { + ret += node.nodeValue; + } + }, false, true); + + return ret; + }; + + return toBBCode(element); + }; + + /** + * Initializer + * @private + */ + base.init = function () { + base.opts = this.opts; + base.elementToBbcode = elementToBbcode; + + // build the BBCode cache + buildBbcodeCache(); + + this.commands = extend( + true, {}, defaultCommandsOverrides, this.commands + ); + + // Add BBCode helper methods + this.toBBCode = base.toSource; + this.fromBBCode = base.toHtml; + }; + + /** + * Converts BBCode into HTML + * + * @param {boolean} asFragment + * @param {string} source + * @param {boolean} [legacyAsFragment] Used by fromBBCode() method + */ + function toHtml(asFragment, source, legacyAsFragment) { + var parser = new BBCodeParser(base.opts.parserOptions); + var html = parser.toHTML( + base.opts.bbcodeTrim ? source.trim() : source + ); + + return (asFragment || legacyAsFragment) ? + removeFirstLastDiv(html) : html; + } + + /** + * Converts HTML into BBCode + * + * @param {boolean} asFragment + * @param {string} html + * @param {!Document} [context] + * @param {!HTMLElement} [parent] + * @return {string} + * @private + */ + function toSource(asFragment, html, context, parent) { + context = context || document; + + var bbcode, elements; + var containerParent = context.createElement('div'); + var container = context.createElement('div'); + var parser = new BBCodeParser(base.opts.parserOptions); + + container.innerHTML = html; + css(containerParent, 'visibility', 'hidden'); + containerParent.appendChild(container); + context.body.appendChild(containerParent); + + if (asFragment) { + // Add text before and after so removeWhiteSpace doesn't remove + // leading and trailing whitespace + containerParent.insertBefore( + context.createTextNode('#'), + containerParent.firstChild + ); + containerParent.appendChild(context.createTextNode('#')); + } + + // Match parents white-space handling + if (parent) { + css(container, 'whiteSpace', css(parent, 'whiteSpace')); + } + + // Remove all nodes with sceditor-ignore class + elements = container.getElementsByClassName('sceditor-ignore'); + while (elements.length) { + elements[0].parentNode.removeChild(elements[0]); + } + + dom.removeWhiteSpace(containerParent); + + bbcode = elementToBbcode(container); + + context.body.removeChild(containerParent); + + bbcode = parser.toBBCode(bbcode, true); + + if (base.opts.bbcodeTrim) { + bbcode = bbcode.trim(); + } + + return bbcode; + }; + + base.toHtml = toHtml.bind(null, false); + base.fragmentToHtml = toHtml.bind(null, true); + base.toSource = toSource.bind(null, false); + base.fragmentToSource = toSource.bind(null, true); + }; + + /** + * Gets a BBCode + * + * @param {string} name + * @return {Object|null} + * @since 2.0.0 + */ + bbcodeFormat.get = function (name) { + return bbcodeHandlers[name] || null; + }; + + /** + * Adds a BBCode to the parser or updates an existing + * BBCode if a BBCode with the specified name already exists. + * + * @param {string} name + * @param {Object} bbcode + * @return {this} + * @since 2.0.0 + */ + bbcodeFormat.set = function (name, bbcode) { + if (name && bbcode) { + // merge any existing command properties + bbcode = extend(bbcodeHandlers[name] || {}, bbcode); + + bbcode.remove = function () { + delete bbcodeHandlers[name]; + }; + + bbcodeHandlers[name] = bbcode; + } + + return this; + }; + + /** + * Renames a BBCode + * + * This does not change the format or HTML handling, those must be + * changed manually. + * + * @param {string} name [description] + * @param {string} newName [description] + * @return {this|false} + * @since 2.0.0 + */ + bbcodeFormat.rename = function (name, newName) { + if (name in bbcodeHandlers) { + bbcodeHandlers[newName] = bbcodeHandlers[name]; + + delete bbcodeHandlers[name]; + } + + return this; + }; + + /** + * Removes a BBCode + * + * @param {string} name + * @return {this} + * @since 2.0.0 + */ + bbcodeFormat.remove = function (name) { + if (name in bbcodeHandlers) { + delete bbcodeHandlers[name]; + } + + return this; + }; + + bbcodeFormat.formatBBCodeString = formatBBCodeString; + + sceditor.formats.bbcode = bbcodeFormat; + sceditor.BBCodeParser = BBCodeParser; +}(sceditor)); diff --git a/public/assets/development/jquery.sceditor.js b/public/assets/development/jquery.sceditor.js new file mode 100644 index 0000000..1eda8e8 --- /dev/null +++ b/public/assets/development/jquery.sceditor.js @@ -0,0 +1,7637 @@ +(function ($) { + 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + + /** + * Check if the passed argument is the + * the passed type. + * + * @param {string} type + * @param {*} arg + * @returns {boolean} + */ + function isTypeof(type, arg) { + return typeof arg === type; + } + + /** + * @type {function(*): boolean} + */ + var isString = isTypeof.bind(null, 'string'); + + /** + * @type {function(*): boolean} + */ + var isUndefined = isTypeof.bind(null, 'undefined'); + + /** + * @type {function(*): boolean} + */ + var isFunction = isTypeof.bind(null, 'function'); + + /** + * @type {function(*): boolean} + */ + var isNumber = isTypeof.bind(null, 'number'); + + + /** + * Returns true if an object has no keys + * + * @param {!Object} obj + * @returns {boolean} + */ + function isEmptyObject(obj) { + return !Object.keys(obj).length; + } + + /** + * Extends the first object with any extra objects passed + * + * If the first argument is boolean and set to true + * it will extend child arrays and objects recursively. + * + * @param {!Object|boolean} targetArg + * @param {...Object} source + * @return {Object} + */ + function extend(targetArg, sourceArg) { + var isTargetBoolean = targetArg === !!targetArg; + var i = isTargetBoolean ? 2 : 1; + var target = isTargetBoolean ? sourceArg : targetArg; + var isDeep = isTargetBoolean ? targetArg : false; + + for (; i < arguments.length; i++) { + var source = arguments[i]; + + // Copy all properties for jQuery compatibility + /* eslint guard-for-in: off */ + for (var key in source) { + var value = source[key]; + + // Skip undefined values to match jQuery and + // skip if target to prevent infinite loop + if (!isUndefined(value)) { + var isObject = value !== null && typeof value === 'object' && + Object.getPrototypeOf(value) === Object.prototype; + var isArray = Array.isArray(value); + + if (isDeep && (isObject || isArray)) { + target[key] = extend( + true, + target[key] || (isArray ? [] : {}), + value + ); + } else { + target[key] = value; + } + } + } + } + + return target; + } + + /** + * Removes an item from the passed array + * + * @param {!Array} arr + * @param {*} item + */ + function arrayRemove(arr, item) { + var i = arr.indexOf(item); + + if (i > -1) { + arr.splice(i, 1); + } + } + + /** + * Iterates over an array or object + * + * @param {!Object|Array} obj + * @param {function(*, *)} fn + */ + function each(obj, fn) { + if (Array.isArray(obj) || 'length' in obj && isNumber(obj.length)) { + for (var i = 0; i < obj.length; i++) { + fn(i, obj[i]); + } + } else { + Object.keys(obj).forEach(function (key) { + fn(key, obj[key]); + }); + } + } + + /** + * Cache of camelCase CSS property names + * @type {Object} + */ + var cssPropertyNameCache = {}; + + /** + * Node type constant for element nodes + * + * @type {number} + */ + var ELEMENT_NODE = 1; + + /** + * Node type constant for text nodes + * + * @type {number} + */ + var TEXT_NODE = 3; + + /** + * Node type constant for comment nodes + * + * @type {number} + */ + + + /** + * Node type document nodes + * + * @type {number} + */ + + + /** + * Node type constant for document fragments + * + * @type {number} + */ + + + function toFloat(value) { + value = parseFloat(value); + + return isFinite(value) ? value : 0; + } + + /** + * Creates an element with the specified attributes + * + * Will create it in the current document unless context + * is specified. + * + * @param {!string} tag + * @param {!Object} [attributes] + * @param {!Document} [context] + * @returns {!HTMLElement} + */ + function createElement(tag, attributes, context) { + var node = (context || document).createElement(tag); + + each(attributes || {}, function (key, value) { + if (key === 'style') { + node.style.cssText = value; + } else if (key in node) { + node[key] = value; + } else { + node.setAttribute(key, value); + } + }); + + return node; + } + + /** + * Returns an array of parents that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {Array} + */ + + + /** + * Gets the first parent node that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {HTMLElement|undefined} + */ + function parent(node, selector) { + var parent = node || {}; + + while ((parent = parent.parentNode) && !/(9|11)/.test(parent.nodeType)) { + if (!selector || is(parent, selector)) { + return parent; + } + } + } + + /** + * Checks the passed node and all parents and + * returns the first matching node if any. + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {HTMLElement|undefined} + */ + function closest(node, selector) { + return is(node, selector) ? node : parent(node, selector); + } + + /** + * Removes the node from the DOM + * + * @param {!HTMLElement} node + */ + function remove(node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + + /** + * Appends child to parent node + * + * @param {!HTMLElement} node + * @param {!HTMLElement} child + */ + function appendChild(node, child) { + node.appendChild(child); + } + + /** + * Finds any child nodes that match the selector + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {NodeList} + */ + function find(node, selector) { + return node.querySelectorAll(selector); + } + + /** + * For on() and off() if to add/remove the event + * to the capture phase + * + * @type {boolean} + */ + var EVENT_CAPTURE = true; + + /** + * For on() and off() if to add/remove the event + * to the bubble phase + * + * @type {boolean} + */ + + + /** + * Adds an event listener for the specified events. + * + * Events should be a space separated list of events. + * + * If selector is specified the handler will only be + * called when the event target matches the selector. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see off() + */ + // eslint-disable-next-line max-params + function on(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector] || function (e) { + var target = e.target; + while (target && target !== node) { + if (is(target, selector)) { + fn.call(target, e); + return; + } + + target = target.parentNode; + } + }; + + fn['_sce-event-' + event + selector] = handler; + } else { + handler = selector; + capture = fn; + } + + node.addEventListener(event, handler, capture || false); + }); + } + + /** + * Removes an event listener for the specified events. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see on() + */ + // eslint-disable-next-line max-params + function off(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector]; + } else { + handler = selector; + capture = fn; + } + + node.removeEventListener(event, handler, capture || false); + }); + } + + /** + * If only attr param is specified it will get + * the value of the attr param. + * + * If value is specified but null the attribute + * will be removed otherwise the attr value will + * be set to the passed value. + * + * @param {!HTMLElement} node + * @param {!string} attr + * @param {?string} [value] + */ + function attr(node, attr, value) { + if (arguments.length < 3) { + return node.getAttribute(attr); + } + + // eslint-disable-next-line eqeqeq, no-eq-null + if (value == null) { + removeAttr(node, attr); + } else { + node.setAttribute(attr, value); + } + } + + /** + * Removes the specified attribute + * + * @param {!HTMLElement} node + * @param {!string} attr + */ + function removeAttr(node, attr) { + node.removeAttribute(attr); + } + + /** + * Sets the passed elements display to none + * + * @param {!HTMLElement} node + */ + function hide(node) { + css(node, 'display', 'none'); + } + + /** + * Sets the passed elements display to default + * + * @param {!HTMLElement} node + */ + function show(node) { + css(node, 'display', ''); + } + + /** + * Toggles an elements visibility + * + * @param {!HTMLElement} node + */ + function toggle(node) { + if (isVisible(node)) { + hide(node); + } else { + show(node); + } + } + + /** + * Gets a computed CSS values or sets an inline CSS value + * + * Rules should be in camelCase format and not + * hyphenated like CSS properties. + * + * @param {!HTMLElement} node + * @param {!Object|string} rule + * @param {string|number} [value] + * @return {string|number|undefined} + */ + function css(node, rule, value) { + if (arguments.length < 3) { + if (isString(rule)) { + return node.nodeType === 1 ? getComputedStyle(node)[rule] : null; + } + + each(rule, function (key, value) { + css(node, key, value); + }); + } else { + // isNaN returns false for null, false and empty strings + // so need to check it's truthy or 0 + var isNumeric = (value || value === 0) && !isNaN(value); + node.style[rule] = isNumeric ? value + 'px' : value; + } + } + + + /** + * Gets or sets thee data attributes on a node + * + * Unlike the jQuery version this only stores data + * in the DOM attributes which means only strings + * can be stored. + * + * @param {Node} node + * @param {string} [key] + * @param {string} [value] + * @return {Object|undefined} + */ + function data(node, key, value) { + var argsLength = arguments.length; + var data = {}; + + if (node.nodeType === ELEMENT_NODE) { + if (argsLength === 1) { + each(node.attributes, function (_, attr) { + if (/^data\-/i.test(attr.name)) { + data[attr.name.substr(5)] = attr.value; + } + }); + + return data; + } + + if (argsLength === 2) { + return attr(node, 'data-' + key); + } + + attr(node, 'data-' + key, String(value)); + } + } + + /** + * Checks if node matches the given selector. + * + * @param {?HTMLElement} node + * @param {string} selector + * @returns {boolean} + */ + function is(node, selector) { + var result = false; + + if (node && node.nodeType === ELEMENT_NODE) { + result = (node.matches || node.msMatchesSelector || + node.webkitMatchesSelector).call(node, selector); + } + + return result; + } + + + /** + * Returns true if node contains child otherwise false. + * + * This differs from the DOM contains() method in that + * if node and child are equal this will return false. + * + * @param {!Node} node + * @param {HTMLElement} child + * @returns {boolean} + */ + function contains(node, child) { + return node !== child && node.contains && node.contains(child); + } + + /** + * @param {Node} node + * @param {string} [selector] + * @returns {?HTMLElement} + */ + function previousElementSibling(node, selector) { + var prev = node.previousElementSibling; + + if (selector && prev) { + return is(prev, selector) ? prev : null; + } + + return prev; + } + + /** + * @param {!Node} node + * @param {!Node} refNode + * @returns {Node} + */ + function insertBefore(node, refNode) { + return refNode.parentNode.insertBefore(node, refNode); + } + + /** + * @param {?HTMLElement} node + * @returns {!Array.} + */ + function classes(node) { + return node.className.trim().split(/\s+/); + } + + /** + * @param {?HTMLElement} node + * @param {string} className + * @returns {boolean} + */ + function hasClass(node, className) { + return is(node, '.' + className); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function addClass(node, className) { + var classList = classes(node); + + if (classList.indexOf(className) < 0) { + classList.push(className); + } + + node.className = classList.join(' '); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function removeClass(node, className) { + var classList = classes(node); + + arrayRemove(classList, className); + + node.className = classList.join(' '); + } + + /** + * Toggles a class on node. + * + * If state is specified and is truthy it will add + * the class. + * + * If state is specified and is falsey it will remove + * the class. + * + * @param {HTMLElement} node + * @param {string} className + * @param {boolean} [state] + */ + function toggleClass(node, className, state) { + state = isUndefined(state) ? !hasClass(node, className) : state; + + if (state) { + addClass(node, className); + } else { + removeClass(node, className); + } + } + + /** + * Gets or sets the width of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function width(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingLeft) + toFloat(cs.paddingRight); + var border = toFloat(cs.borderLeftWidth) + toFloat(cs.borderRightWidth); + + return node.offsetWidth - padding - border; + } + + css(node, 'width', value); + } + + /** + * Gets or sets the height of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function height(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingTop) + toFloat(cs.paddingBottom); + var border = toFloat(cs.borderTopWidth) + toFloat(cs.borderBottomWidth); + + return node.offsetHeight - padding - border; + } + + css(node, 'height', value); + } + + /** + * Triggers a custom event with the specified name and + * sets the detail property to the data object passed. + * + * @param {HTMLElement} node + * @param {string} eventName + * @param {Object} [data] + */ + function trigger(node, eventName, data) { + var event; + + if (isFunction(window.CustomEvent)) { + event = new CustomEvent(eventName, { + bubbles: true, + cancelable: true, + detail: data + }); + } else { + event = node.ownerDocument.createEvent('CustomEvent'); + event.initCustomEvent(eventName, true, true, data); + } + + node.dispatchEvent(event); + } + + /** + * Returns if a node is visible. + * + * @param {HTMLElement} + * @returns {boolean} + */ + function isVisible(node) { + return !!node.getClientRects().length; + } + + /** + * Convert CSS property names into camel case + * + * @param {string} string + * @returns {string} + */ + function camelCase(string) { + return string + .replace(/^-ms-/, 'ms-') + .replace(/-(\w)/g, function (match, char) { + return char.toUpperCase(); + }); + } + + + /** + * Loop all child nodes of the passed node + * + * The function should accept 1 parameter being the node. + * If the function returns false the loop will be exited. + * + * @param {HTMLElement} node + * @param {function} func Callback which is called with every + * child node as the first argument. + * @param {boolean} innermostFirst If the innermost node should be passed + * to the function before it's parents. + * @param {boolean} siblingsOnly If to only traverse the nodes siblings + * @param {boolean} [reverse=false] If to traverse the nodes in reverse + */ + // eslint-disable-next-line max-params + function traverse(node, func, innermostFirst, siblingsOnly, reverse) { + node = reverse ? node.lastChild : node.firstChild; + + while (node) { + var next = reverse ? node.previousSibling : node.nextSibling; + + if ( + (!innermostFirst && func(node) === false) || + (!siblingsOnly && traverse( + node, func, innermostFirst, siblingsOnly, reverse + ) === false) || + (innermostFirst && func(node) === false) + ) { + return false; + } + + node = next; + } + } + + /** + * Like traverse but loops in reverse + * @see traverse + */ + function rTraverse(node, func, innermostFirst, siblingsOnly) { + traverse(node, func, innermostFirst, siblingsOnly, true); + } + + /** + * Parses HTML into a document fragment + * + * @param {string} html + * @param {Document} [context] + * @since 1.4.4 + * @return {DocumentFragment} + */ + function parseHTML(html, context) { + context = context || document; + + var ret = context.createDocumentFragment(); + var tmp = createElement('div', {}, context); + + tmp.innerHTML = html; + + while (tmp.firstChild) { + appendChild(ret, tmp.firstChild); + } + + return ret; + } + + /** + * Checks if an element has any styling. + * + * It has styling if it is not a plain
    or

    or + * if it has a class, style attribute or data. + * + * @param {HTMLElement} elm + * @return {boolean} + * @since 1.4.4 + */ + function hasStyling(node) { + return node && (!is(node, 'p,div') || node.className || + attr(node, 'style') || !isEmptyObject(data(node))); + } + + /** + * Converts an element from one type to another. + * + * For example it can convert the element to + * + * @param {HTMLElement} element + * @param {string} toTagName + * @return {HTMLElement} + * @since 1.4.4 + */ + function convertElement(element, toTagName) { + var newElement = createElement(toTagName, {}, element.ownerDocument); + + each(element.attributes, function (_, attribute) { + // Some browsers parse invalid attributes names like + // 'size"2' which throw an exception when set, just + // ignore these. + try { + attr(newElement, attribute.name, attribute.value); + } catch (ex) {} + }); + + while (element.firstChild) { + appendChild(newElement, element.firstChild); + } + + element.parentNode.replaceChild(newElement, element); + + return newElement; + } + + /** + * List of block level elements separated by bars (|) + * + * @type {string} + */ + var blockLevelList = '|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|' + + 'form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|'; + + /** + * List of elements that do not allow children separated by bars (|) + * + * @param {Node} node + * @return {boolean} + * @since 1.4.5 + */ + function canHaveChildren(node) { + // 1 = Element + // 9 = Document + // 11 = Document Fragment + if (!/11?|9/.test(node.nodeType)) { + return false; + } + + // List of empty HTML tags separated by bar (|) character. + // Source: http://www.w3.org/TR/html4/index/elements.html + // Source: http://www.w3.org/TR/html5/syntax.html#void-elements + return ('|iframe|area|base|basefont|br|col|frame|hr|img|input|wbr' + + '|isindex|link|meta|param|command|embed|keygen|source|track|' + + 'object|').indexOf('|' + node.nodeName.toLowerCase() + '|') < 0; + } + + /** + * Checks if an element is inline + * + * @param {HTMLElement} elm + * @param {boolean} [includeCodeAsBlock=false] + * @return {boolean} + */ + function isInline(elm, includeCodeAsBlock) { + var tagName, + nodeType = (elm || {}).nodeType || TEXT_NODE; + + if (nodeType !== ELEMENT_NODE) { + return nodeType === TEXT_NODE; + } + + tagName = elm.tagName.toLowerCase(); + + if (tagName === 'code') { + return !includeCodeAsBlock; + } + + return blockLevelList.indexOf('|' + tagName + '|') < 0; + } + + /** + * Copy the CSS from 1 node to another. + * + * Only copies CSS defined on the element e.g. style attr. + * + * @param {HTMLElement} from + * @param {HTMLElement} to + */ + function copyCSS(from, to) { + to.style.cssText = from.style.cssText + to.style.cssText; + } + + /** + * Fixes block level elements inside in inline elements. + * + * Also fixes invalid list nesting by placing nested lists + * inside the previous li tag or wrapping them in an li tag. + * + * @param {HTMLElement} node + */ + function fixNesting(node) { + var getLastInlineParent = function (node) { + while (isInline(node.parentNode, true)) { + node = node.parentNode; + } + + return node; + }; + + traverse(node, function (node) { + var list = 'ul,ol', + isBlock = !isInline(node, true); + + // Any blocklevel element inside an inline element needs fixing. + if (isBlock && isInline(node.parentNode, true)) { + var parent = getLastInlineParent(node), + before = extractContents(parent, node), + middle = node; + + // copy current styling so when moved out of the parent + // it still has the same styling + copyCSS(parent, middle); + + insertBefore(before, parent); + insertBefore(middle, parent); + } + + // Fix invalid nested lists which should be wrapped in an li tag + if (isBlock && is(node, list) && is(node.parentNode, list)) { + var li = previousElementSibling(node, 'li'); + + if (!li) { + li = createElement('li'); + insertBefore(li, node); + } + + appendChild(li, node); + } + }); + } + + /** + * Finds the common parent of two nodes + * + * @param {!HTMLElement} node1 + * @param {!HTMLElement} node2 + * @return {?HTMLElement} + */ + function findCommonAncestor(node1, node2) { + while ((node1 = node1.parentNode)) { + if (contains(node1, node2)) { + return node1; + } + } + } + + /** + * @param {?Node} + * @param {boolean} [previous=false] + * @returns {?Node} + */ + function getSibling(node, previous) { + if (!node) { + return null; + } + + return (previous ? node.previousSibling : node.nextSibling) || + getSibling(node.parentNode, previous); + } + + /** + * Removes unused whitespace from the root and all it's children. + * + * @param {!HTMLElement} root + * @since 1.4.3 + */ + function removeWhiteSpace(root) { + var nodeValue, nodeType, next, previous, previousSibling, + nextNode, trimStart, + cssWhiteSpace = css(root, 'whiteSpace'), + // Preserve newlines if is pre-line + preserveNewLines = /line$/i.test(cssWhiteSpace), + node = root.firstChild; + + // Skip pre & pre-wrap with any vendor prefix + if (/pre(\-wrap)?$/i.test(cssWhiteSpace)) { + return; + } + + while (node) { + nextNode = node.nextSibling; + nodeValue = node.nodeValue; + nodeType = node.nodeType; + + if (nodeType === ELEMENT_NODE && node.firstChild) { + removeWhiteSpace(node); + } + + if (nodeType === TEXT_NODE) { + next = getSibling(node); + previous = getSibling(node, true); + trimStart = false; + + while (hasClass(previous, 'sceditor-ignore')) { + previous = getSibling(previous, true); + } + + // If previous sibling isn't inline or is a textnode that + // ends in whitespace, time the start whitespace + if (isInline(node) && previous) { + previousSibling = previous; + + while (previousSibling.lastChild) { + previousSibling = previousSibling.lastChild; + + // eslint-disable-next-line max-depth + while (hasClass(previousSibling, 'sceditor-ignore')) { + previousSibling = getSibling(previousSibling, true); + } + } + + trimStart = previousSibling.nodeType === TEXT_NODE ? + /[\t\n\r ]$/.test(previousSibling.nodeValue) : + !isInline(previousSibling); + } + + // Clear zero width spaces + nodeValue = nodeValue.replace(/\u200B/g, ''); + + // Strip leading whitespace + if (!previous || !isInline(previous) || trimStart) { + nodeValue = nodeValue.replace( + preserveNewLines ? /^[\t ]+/ : /^[\t\n\r ]+/, + '' + ); + } + + // Strip trailing whitespace + if (!next || !isInline(next)) { + nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+$/ : /[\t\n\r ]+$/, + '' + ); + } + + // Remove empty text nodes + if (!nodeValue.length) { + remove(node); + } else { + node.nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+/g : /[\t\n\r ]+/g, + ' ' + ); + } + } + + node = nextNode; + } + } + + /** + * Extracts all the nodes between the start and end nodes + * + * @param {HTMLElement} startNode The node to start extracting at + * @param {HTMLElement} endNode The node to stop extracting at + * @return {DocumentFragment} + */ + function extractContents(startNode, endNode) { + var range = startNode.ownerDocument.createRange(); + + range.setStartBefore(startNode); + range.setEndAfter(endNode); + + return range.extractContents(); + } + + /** + * Gets the offset position of an element + * + * @param {HTMLElement} node + * @return {Object} An object with left and top properties + */ + function getOffset(node) { + var left = 0, + top = 0; + + while (node) { + left += node.offsetLeft; + top += node.offsetTop; + node = node.offsetParent; + } + + return { + left: left, + top: top + }; + } + + /** + * Gets the value of a CSS property from the elements style attribute + * + * @param {HTMLElement} elm + * @param {string} property + * @return {string} + */ + function getStyle(elm, property) { + var direction, styleValue, + elmStyle = elm.style; + + if (!cssPropertyNameCache[property]) { + cssPropertyNameCache[property] = camelCase(property); + } + + property = cssPropertyNameCache[property]; + styleValue = elmStyle[property]; + + // Add an exception for text-align + if ('textAlign' === property) { + direction = elmStyle.direction; + styleValue = styleValue || css(elm, property); + + if (css(elm.parentNode, property) === styleValue || + css(elm, 'display') !== 'block' || is(elm, 'hr,th')) { + return ''; + } + + // IE changes text-align to the same as the current direction + // so skip unless its not the same + if ((/right/i.test(styleValue) && direction === 'rtl') || + (/left/i.test(styleValue) && direction === 'ltr')) { + return ''; + } + } + + return styleValue; + } + + /** + * Tests if an element has a style. + * + * If values are specified it will check that the styles value + * matches one of the values + * + * @param {HTMLElement} elm + * @param {string} property + * @param {string|array} [values] + * @return {boolean} + */ + function hasStyle(elm, property, values) { + var styleValue = getStyle(elm, property); + + if (!styleValue) { + return false; + } + + return !values || styleValue === values || + (Array.isArray(values) && values.indexOf(styleValue) > -1); + } + + /** + * Default options for SCEditor + * @type {Object} + */ + var defaultOptions = { + /** @lends jQuery.sceditor.defaultOptions */ + /** + * Toolbar buttons order and groups. Should be comma separated and + * have a bar | to separate groups + * + * @type {string} + */ + toolbar: 'bold,italic,underline,strike,subscript,superscript|' + + 'left,center,right,justify|font,size,color,removeformat|' + + 'cut,copy,pastetext|bulletlist,orderedlist,indent,outdent|' + + 'table|code,quote|horizontalrule,image,email,link,unlink|' + + 'emoticon,youtube,date,time|ltr,rtl|print,maximize,source', + + /** + * Comma separated list of commands to excludes from the toolbar + * + * @type {string} + */ + toolbarExclude: null, + + /** + * Stylesheet to include in the WYSIWYG editor. This is what will style + * the WYSIWYG elements + * + * @type {string} + */ + style: 'jquery.sceditor.default.css', + + /** + * Comma separated list of fonts for the font selector + * + * @type {string} + */ + fonts: 'Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,' + + 'Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana', + + /** + * Colors should be comma separated and have a bar | to signal a new + * column. + * + * If null the colors will be auto generated. + * + * @type {string} + */ + colors: '#000000,#44B8FF,#1E92F7,#0074D9,#005DC2,#00369B,#b3d5f4|' + + '#444444,#C3FFFF,#9DF9FF,#7FDBFF,#68C4E8,#419DC1,#d9f4ff|' + + '#666666,#72FF84,#4CEA5E,#2ECC40,#17B529,#008E02,#c0f0c6|' + + '#888888,#FFFF44,#FFFA1E,#FFDC00,#E8C500,#C19E00,#fff5b3|' + + '#aaaaaa,#FFC95F,#FFA339,#FF851B,#E86E04,#C14700,#ffdbbb|' + + '#cccccc,#FF857A,#FF5F54,#FF4136,#E82A1F,#C10300,#ffc6c3|' + + '#eeeeee,#FF56FF,#FF30DC,#F012BE,#D900A7,#B20080,#fbb8ec|' + + '#ffffff,#F551FF,#CF2BE7,#B10DC9,#9A00B2,#9A00B2,#e8b6ef', + + /** + * The locale to use. + * @type {string} + */ + locale: attr(document.documentElement, 'lang') || 'en', + + /** + * The Charset to use + * @type {string} + */ + charset: 'utf-8', + + /** + * Compatibility mode for emoticons. + * + * Helps if you have emoticons such as :/ which would put an emoticon + * inside http:// + * + * This mode requires emoticons to be surrounded by whitespace or end of + * line chars. This mode has limited As You Type emoticon conversion + * support. It will not replace AYT for end of line chars, only + * emoticons surrounded by whitespace. They will still be replaced + * correctly when loaded just not AYT. + * + * @type {boolean} + */ + emoticonsCompat: false, + + /** + * If to enable emoticons. Can be changes at runtime using the + * emoticons() method. + * + * @type {boolean} + * @since 1.4.2 + */ + emoticonsEnabled: true, + + /** + * Emoticon root URL + * + * @type {string} + */ + emoticonsRoot: '', + emoticons: { + dropdown: { + ':)': 'emoticons/smile.png', + ':angel:': 'emoticons/angel.png', + ':angry:': 'emoticons/angry.png', + '8-)': 'emoticons/cool.png', + ':\'(': 'emoticons/cwy.png', + ':ermm:': 'emoticons/ermm.png', + ':D': 'emoticons/grin.png', + '<3': 'emoticons/heart.png', + ':(': 'emoticons/sad.png', + ':O': 'emoticons/shocked.png', + ':P': 'emoticons/tongue.png', + ';)': 'emoticons/wink.png' + }, + more: { + ':alien:': 'emoticons/alien.png', + ':blink:': 'emoticons/blink.png', + ':blush:': 'emoticons/blush.png', + ':cheerful:': 'emoticons/cheerful.png', + ':devil:': 'emoticons/devil.png', + ':dizzy:': 'emoticons/dizzy.png', + ':getlost:': 'emoticons/getlost.png', + ':happy:': 'emoticons/happy.png', + ':kissing:': 'emoticons/kissing.png', + ':ninja:': 'emoticons/ninja.png', + ':pinch:': 'emoticons/pinch.png', + ':pouty:': 'emoticons/pouty.png', + ':sick:': 'emoticons/sick.png', + ':sideways:': 'emoticons/sideways.png', + ':silly:': 'emoticons/silly.png', + ':sleeping:': 'emoticons/sleeping.png', + ':unsure:': 'emoticons/unsure.png', + ':woot:': 'emoticons/w00t.png', + ':wassat:': 'emoticons/wassat.png' + }, + hidden: { + ':whistling:': 'emoticons/whistling.png', + ':love:': 'emoticons/wub.png' + } + }, + + /** + * Width of the editor. Set to null for automatic with + * + * @type {?number} + */ + width: null, + + /** + * Height of the editor including toolbar. Set to null for automatic + * height + * + * @type {?number} + */ + height: null, + + /** + * If to allow the editor to be resized + * + * @type {boolean} + */ + resizeEnabled: true, + + /** + * Min resize to width, set to null for half textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinWidth: null, + /** + * Min resize to height, set to null for half textarea height or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinHeight: null, + /** + * Max resize to height, set to null for double textarea height or -1 + * for unlimited + * + * @type {?number} + */ + resizeMaxHeight: null, + /** + * Max resize to width, set to null for double textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMaxWidth: null, + /** + * If resizing by height is enabled + * + * @type {boolean} + */ + resizeHeight: true, + /** + * If resizing by width is enabled + * + * @type {boolean} + */ + resizeWidth: true, + + /** + * Date format, will be overridden if locale specifies one. + * + * The words year, month and day will be replaced with the users current + * year, month and day. + * + * @type {string} + */ + dateFormat: 'year-month-day', + + /** + * Element to inset the toolbar into. + * + * @type {HTMLElement} + */ + toolbarContainer: null, + + /** + * If to enable paste filtering. This is currently experimental, please + * report any issues. + * + * @type {boolean} + */ + enablePasteFiltering: false, + + /** + * If to completely disable pasting into the editor + * + * @type {boolean} + */ + disablePasting: false, + + /** + * If the editor is read only. + * + * @type {boolean} + */ + readOnly: false, + + /** + * If to set the editor to right-to-left mode. + * + * If set to null the direction will be automatically detected. + * + * @type {boolean} + */ + rtl: false, + + /** + * If to auto focus the editor on page load + * + * @type {boolean} + */ + autofocus: false, + + /** + * If to auto focus the editor to the end of the content + * + * @type {boolean} + */ + autofocusEnd: true, + + /** + * If to auto expand the editor to fix the content + * + * @type {boolean} + */ + autoExpand: false, + + /** + * If to auto update original textbox on blur + * + * @type {boolean} + */ + autoUpdate: false, + + /** + * If to enable the browsers built in spell checker + * + * @type {boolean} + */ + spellcheck: true, + + /** + * If to run the source editor when there is no WYSIWYG support. Only + * really applies to mobile OS's. + * + * @type {boolean} + */ + runWithoutWysiwygSupport: false, + + /** + * If to load the editor in source mode and still allow switching + * between WYSIWYG and source mode + * + * @type {boolean} + */ + startInSourceMode: false, + + /** + * Optional ID to give the editor. + * + * @type {string} + */ + id: null, + + /** + * Comma separated list of plugins + * + * @type {string} + */ + plugins: '', + + /** + * z-index to set the editor container to. Needed for jQuery UI dialog. + * + * @type {?number} + */ + zIndex: null, + + /** + * If to trim the BBCode. Removes any spaces at the start and end of the + * BBCode string. + * + * @type {boolean} + */ + bbcodeTrim: false, + + /** + * If to disable removing block level elements by pressing backspace at + * the start of them + * + * @type {boolean} + */ + disableBlockRemove: false, + + /** + * BBCode parser options, only applies if using the editor in BBCode + * mode. + * + * See SCEditor.BBCodeParser.defaults for list of valid options + * + * @type {Object} + */ + parserOptions: { }, + + /** + * CSS that will be added to the to dropdown menu (eg. z-index) + * + * @type {Object} + */ + dropDownCss: { } + }; + + var USER_AGENT = navigator.userAgent; + + /** + * Detects the version of IE is being used if any. + * + * Will be the IE version number or undefined if the + * browser is not IE. + * + * Source: https://gist.github.com/527683 with extra code + * for IE 10 & 11 detection. + * + * @function + * @name ie + * @type {number} + */ + var ie = (function () { + var undef, + v = 3, + doc = document, + div = doc.createElement('div'), + all = div.getElementsByTagName('i'); + + do { + div.innerHTML = ''; + } while (all[0]); + + // Detect IE 10 as it doesn't support conditional comments. + if ((doc.documentMode && doc.all && window.atob)) { + v = 10; + } + + // Detect IE 11 + if (v === 4 && doc.documentMode) { + v = 11; + } + + return v > 4 ? v : undef; + }()); + + var edge = '-ms-ime-align' in document.documentElement.style; + + /** + * Detects if the browser is iOS + * + * Needed to fix iOS specific bugs + * + * @function + * @name ios + * @memberOf jQuery.sceditor + * @type {boolean} + */ + var ios = /iPhone|iPod|iPad| wosbrowser\//i.test(USER_AGENT); + + /** + * If the browser supports WYSIWYG editing (e.g. older mobile browsers). + * + * @function + * @name isWysiwygSupported + * @return {boolean} + */ + var isWysiwygSupported = (function () { + var match, isUnsupported; + + var div = document.createElement('div'); + div.contentEditable = true ; + + // Check if the contentEditable attribute is supported + if (!('contentEditable' in document.documentElement) || + div.contentEditable !== 'true') { + return false; + } + + // I think blackberry supports contentEditable or will at least + // give a valid value for the contentEditable detection above + // so it isn't included in the below tests. + + // I hate having to do UA sniffing but some mobile browsers say they + // support contentediable when it isn't usable, i.e. you can't enter + // text. + // This is the only way I can think of to detect them which is also how + // every other editor I've seen deals with this issue. + + // Exclude Opera mobile and mini + isUnsupported = /Opera Mobi|Opera Mini/i.test(USER_AGENT); + + if (/Android/i.test(USER_AGENT)) { + isUnsupported = true; + + if (/Safari/.test(USER_AGENT)) { + // Android browser 534+ supports content editable + // This also matches Chrome which supports content editable too + match = /Safari\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + } + + // The current version of Amazon Silk supports it, older versions didn't + // As it uses webkit like Android, assume it's the same and started + // working at versions >= 534 + if (/ Silk\//i.test(USER_AGENT)) { + match = /AppleWebKit\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + + // iOS 5+ supports content editable + if (ios) { + // Block any version <= 4_x(_x) + isUnsupported = /OS [0-4](_\d)+ like Mac/i.test(USER_AGENT); + } + + // Firefox does support WYSIWYG on mobiles so override + // any previous value if using FF + if (/Firefox/i.test(USER_AGENT)) { + isUnsupported = false; + } + + if (/OneBrowser/i.test(USER_AGENT)) { + isUnsupported = false; + } + + // UCBrowser works but doesn't give a unique user agent + if (navigator.vendor === 'UCWEB') { + isUnsupported = false; + } + + // IE <= 9 is not supported any more + if (ie <= 9) { + isUnsupported = true; + } + + return !isUnsupported; + }()); + + // Must start with a valid scheme + // ^ + // Schemes that are considered safe + // (https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):| + // Relative schemes (//:) are considered safe + // (\\/\\/)| + // Image data URI's are considered safe + // data:image\\/(png|bmp|gif|p?jpe?g); + var VALID_SCHEME_REGEX = + /^(https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):|(\/\/)|data:image\/(png|bmp|gif|p?jpe?g);/i; + + /** + * Escapes a string so it's safe to use in regex + * + * @param {string} str + * @return {string} + */ + function regex(str) { + return str.replace(/([\-.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + } + + /** + * Escapes all HTML entities in a string + * + * If noQuotes is set to false, all single and double + * quotes will also be escaped + * + * @param {string} str + * @param {boolean} [noQuotes=true] + * @return {string} + * @since 1.4.1 + */ + function entities(str, noQuotes) { + if (!str) { + return str; + } + + var replacements = { + '&': '&', + '<': '<', + '>': '>', + ' ': '  ', + '\r\n': '
    ', + '\r': '
    ', + '\n': '
    ' + }; + + if (noQuotes !== false) { + replacements['"'] = '"'; + replacements['\''] = '''; + replacements['`'] = '`'; + } + + str = str.replace(/ {2}|\r\n|[&<>\r\n'"`]/g, function (match) { + return replacements[match] || match; + }); + + return str; + } + + /** + * Escape URI scheme. + * + * Appends the current URL to a url if it has a scheme that is not: + * + * http + * https + * sftp + * ftp + * mailto + * spotify + * skype + * ssh + * teamspeak + * tel + * // + * data:image/(png|jpeg|jpg|pjpeg|bmp|gif); + * + * **IMPORTANT**: This does not escape any HTML in a url, for + * that use the escape.entities() method. + * + * @param {string} url + * @return {string} + * @since 1.4.5 + */ + function uriScheme(url) { + var path, + // If there is a : before a / then it has a scheme + hasScheme = /^[^\/]*:/i, + location = window.location; + + // Has no scheme or a valid scheme + if ((!url || !hasScheme.test(url)) || VALID_SCHEME_REGEX.test(url)) { + return url; + } + + path = location.pathname.split('/'); + path.pop(); + + return location.protocol + '//' + + location.host + + path.join('/') + '/' + + url; + } + + /** + * HTML templates used by the editor and default commands + * @type {Object} + * @private + */ + var _templates = { + html: + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

    ' + + '', + + toolbarButton: '' + + '
    {dispName}
    ', + + emoticon: '', + + fontOpt: '{font}', + + sizeOpt: '{size}', + + pastetext: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + table: + '
    ' + + '
    ' + + '
    ', + + image: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + email: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + link: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtubeMenu: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtube: + '' + }; + + /** + * Replaces any params in a template with the passed params. + * + * If createHtml is passed it will return a DocumentFragment + * containing the parsed template. + * + * @param {string} name + * @param {Object} [params] + * @param {boolean} [createHtml] + * @returns {string|DocumentFragment} + * @private + */ + function _tmpl (name, params, createHtml) { + var template = _templates[name]; + + Object.keys(params).forEach(function (name) { + template = template.replace( + new RegExp(regex('{' + name + '}'), 'g'), params[name] + ); + }); + + if (createHtml) { + template = parseHTML(template); + } + + return template; + } + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX = ie && ie < 11; + + /** + * Fixes a bug in FF where it sometimes wraps + * new lines in their own list item. + * See issue #359 + */ + function fixFirefoxListBug(editor) { + // Only apply to Firefox as will break other browsers. + if ('mozHidden' in document) { + var node = editor.getBody(); + var next; + + while (node) { + next = node; + + if (next.firstChild) { + next = next.firstChild; + } else { + + while (next && !next.nextSibling) { + next = next.parentNode; + } + + if (next) { + next = next.nextSibling; + } + } + + if (node.nodeType === 3 && /[\n\r\t]+/.test(node.nodeValue)) { + // Only remove if newlines are collapsed + if (!/^pre/.test(css(node.parentNode, 'whiteSpace'))) { + remove(node); + } + } + + node = next; + } + } + } + + + /** + * Map of all the commands for SCEditor + * @type {Object} + * @name commands + * @memberOf jQuery.sceditor + */ + var defaultCmds = { + // START_COMMAND: Bold + bold: { + exec: 'bold', + tooltip: 'Bold', + shortcut: 'Ctrl+B' + }, + // END_COMMAND + // START_COMMAND: Italic + italic: { + exec: 'italic', + tooltip: 'Italic', + shortcut: 'Ctrl+I' + }, + // END_COMMAND + // START_COMMAND: Underline + underline: { + exec: 'underline', + tooltip: 'Underline', + shortcut: 'Ctrl+U' + }, + // END_COMMAND + // START_COMMAND: Strikethrough + strike: { + exec: 'strikethrough', + tooltip: 'Strikethrough' + }, + // END_COMMAND + // START_COMMAND: Subscript + subscript: { + exec: 'subscript', + tooltip: 'Subscript' + }, + // END_COMMAND + // START_COMMAND: Superscript + superscript: { + exec: 'superscript', + tooltip: 'Superscript' + }, + // END_COMMAND + + // START_COMMAND: Left + left: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'left' || align === (isLtr ? 'start' : 'end'); + } + }, + exec: 'justifyleft', + tooltip: 'Align left' + }, + // END_COMMAND + // START_COMMAND: Centre + center: { + exec: 'justifycenter', + tooltip: 'Center' + }, + // END_COMMAND + // START_COMMAND: Right + right: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'right' || align === (isLtr ? 'end' : 'start'); + } + }, + exec: 'justifyright', + tooltip: 'Align right' + }, + // END_COMMAND + // START_COMMAND: Justify + justify: { + exec: 'justifyfull', + tooltip: 'Justify' + }, + // END_COMMAND + + // START_COMMAND: Font + font: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'font')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.opts.fonts.split(',').forEach(function (font) { + appendChild(content, _tmpl('fontOpt', { + font: font + }, true)); + }); + + editor.createDropDown(caller, 'font-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.font._dropDown(editor, caller, function (fontName) { + editor.execCommand('fontname', fontName); + }); + }, + tooltip: 'Font Name' + }, + // END_COMMAND + // START_COMMAND: Size + size: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'size')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + for (var i = 1; i <= 7; i++) { + appendChild(content, _tmpl('sizeOpt', { + size: i + }, true)); + } + + editor.createDropDown(caller, 'fontsize-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.size._dropDown(editor, caller, function (fontSize) { + editor.execCommand('fontsize', fontSize); + }); + }, + tooltip: 'Font Size' + }, + // END_COMMAND + // START_COMMAND: Colour + color: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'), + html = '', + cmd = defaultCmds.color; + + if (!cmd._htmlCache) { + editor.opts.colors.split('|').forEach(function (column) { + html += '
    '; + + column.split(',').forEach(function (color) { + html += + ''; + }); + + html += '
    '; + }); + + cmd._htmlCache = html; + } + + appendChild(content, parseHTML(cmd._htmlCache)); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'color')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'color-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.color._dropDown(editor, caller, function (color) { + editor.execCommand('forecolor', color); + }); + }, + tooltip: 'Font Color' + }, + // END_COMMAND + // START_COMMAND: Remove Format + removeformat: { + exec: 'removeformat', + tooltip: 'Remove Formatting' + }, + // END_COMMAND + + // START_COMMAND: Cut + cut: { + exec: 'cut', + tooltip: 'Cut', + errorMessage: 'Your browser does not allow the cut command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-X' + }, + // END_COMMAND + // START_COMMAND: Copy + copy: { + exec: 'copy', + tooltip: 'Copy', + errorMessage: 'Your browser does not allow the copy command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-C' + }, + // END_COMMAND + // START_COMMAND: Paste + paste: { + exec: 'paste', + tooltip: 'Paste', + errorMessage: 'Your browser does not allow the paste command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-V' + }, + // END_COMMAND + // START_COMMAND: Paste Text + pastetext: { + exec: function (caller) { + var val, + content = createElement('div'), + editor = this; + + appendChild(content, _tmpl('pastetext', { + label: editor._( + 'Paste your text inside the following box:' + ), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + val = find(content, '#txt')[0].value; + + if (val) { + editor.wysiwygEditorInsertText(val); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'pastetext', content); + }, + tooltip: 'Paste Text' + }, + // END_COMMAND + // START_COMMAND: Bullet List + bulletlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertunorderedlist'); + }, + tooltip: 'Bullet list' + }, + // END_COMMAND + // START_COMMAND: Ordered List + orderedlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertorderedlist'); + }, + tooltip: 'Numbered list' + }, + // END_COMMAND + // START_COMMAND: Indent + indent: { + state: function (parent$$1, firstBlock) { + // Only works with lists, for now + var range, startParent, endParent; + + if (is(firstBlock, 'li')) { + return 0; + } + + if (is(firstBlock, 'ul,ol,menu')) { + // if the whole list is selected, then this must be + // invalidated because the browser will place a + //
    there + range = this.getRangeHelper().selectedRange(); + + startParent = range.startContainer.parentNode; + endParent = range.endContainer.parentNode; + + // TODO: could use nodeType for this? + // Maybe just check the firstBlock contains both the start + //and end containers + + // Select the tag, not the textNode + // (that's why the parentNode) + if (startParent !== + startParent.parentNode.firstElementChild || + // work around a bug in FF + (is(endParent, 'li') && endParent !== + endParent.parentNode.lastElementChild)) { + return 0; + } + } + + return -1; + }, + exec: function () { + var editor = this, + block = editor.getRangeHelper().getFirstBlockParent(); + + editor.focus(); + + // An indent system is quite complicated as there are loads + // of complications and issues around how to indent text + // As default, let's just stay with indenting the lists, + // at least, for now. + if (closest(block, 'ul,ol,menu')) { + editor.execCommand('indent'); + } + }, + tooltip: 'Add indent' + }, + // END_COMMAND + // START_COMMAND: Outdent + outdent: { + state: function (parents$$1, firstBlock) { + return closest(firstBlock, 'ul,ol,menu') ? 0 : -1; + }, + exec: function () { + var block = this.getRangeHelper().getFirstBlockParent(); + if (closest(block, 'ul,ol,menu')) { + this.execCommand('outdent'); + } + }, + tooltip: 'Remove one indent' + }, + // END_COMMAND + + // START_COMMAND: Table + table: { + exec: function (caller) { + var editor = this, + content = createElement('div'); + + appendChild(content, _tmpl('table', { + rows: editor._('Rows:'), + cols: editor._('Cols:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var rows = Number(find(content, '#rows')[0].value), + cols = Number(find(content, '#cols')[0].value), + html = ''; + + if (rows > 0 && cols > 0) { + html += Array(rows + 1).join( + '' + + Array(cols + 1).join( + '' + ) + + '' + ); + + html += '
    ' + (IE_BR_FIX ? '' : '
    ') + '
    '; + + editor.wysiwygEditorInsertHtml(html); + editor.closeDropDown(true); + e.preventDefault(); + } + }); + + editor.createDropDown(caller, 'inserttable', content); + }, + tooltip: 'Insert a table' + }, + // END_COMMAND + + // START_COMMAND: Horizontal Rule + horizontalrule: { + exec: 'inserthorizontalrule', + tooltip: 'Insert a horizontal rule' + }, + // END_COMMAND + + // START_COMMAND: Code + code: { + exec: function () { + this.wysiwygEditorInsertHtml( + '', + (IE_BR_FIX ? '' : '
    ') + '
    ' + ); + }, + tooltip: 'Code' + }, + // END_COMMAND + + // START_COMMAND: Image + image: { + _dropDown: function (editor, caller, selected, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('image', { + url: editor._('URL:'), + width: editor._('Width (optional):'), + height: editor._('Height (optional):'), + insert: editor._('Insert') + }, true)); + + + var urlInput = find(content, '#image')[0]; + + urlInput.value = selected; + + on(content, 'click', '.button', function (e) { + if (urlInput.value) { + cb( + urlInput.value, + find(content, '#width')[0].value, + find(content, '#height')[0].value + ); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertimage', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.image._dropDown( + editor, + caller, + '', + function (url, width$$1, height$$1) { + var attrs = ''; + + if (width$$1) { + attrs += ' width="' + width$$1 + '"'; + } + + if (height$$1) { + attrs += ' height="' + height$$1 + '"'; + } + + editor.wysiwygEditorInsertHtml( + '' + ); + } + ); + }, + tooltip: 'Insert an image' + }, + // END_COMMAND + + // START_COMMAND: E-mail + email: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('email', { + label: editor._('E-mail:'), + desc: editor._('Description (optional):'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var email = find(content, '#email')[0].value; + + if (email) { + cb(email, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertemail', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.email._dropDown( + editor, + caller, + function (email, text) { + // needed for IE to reset the last range + editor.focus(); + + if (!editor.getRangeHelper().selectedHtml() || text) { + editor.wysiwygEditorInsertHtml( + '' + + (text || email) + + '' + ); + } else { + editor.execCommand('createlink', 'mailto:' + email); + } + } + ); + }, + tooltip: 'Insert an email' + }, + // END_COMMAND + + // START_COMMAND: Link + link: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('link', { + url: editor._('URL:'), + desc: editor._('Description (optional):'), + ins: editor._('Insert') + }, true)); + + var linkInput = find(content, '#link')[0]; + + function insertUrl(e) { + if (linkInput.value) { + cb(linkInput.value, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + } + + on(content, 'click', '.button', insertUrl); + on(content, 'keypress', function (e) { + // 13 = enter key + if (e.which === 13 && linkInput.value) { + insertUrl(e); + } + }, EVENT_CAPTURE); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.link._dropDown(editor, caller, function (url, text) { + // needed for IE to restore the last range + editor.focus(); + + // If there is no selected text then must set the URL as + // the text. Most browsers do this automatically, sadly + // IE doesn't. + if (text || !editor.getRangeHelper().selectedHtml()) { + text = text || url; + + editor.wysiwygEditorInsertHtml( + '' + text + '' + ); + } else { + editor.execCommand('createlink', url); + } + }); + }, + tooltip: 'Insert a link' + }, + // END_COMMAND + + // START_COMMAND: Unlink + unlink: { + state: function () { + return closest(this.currentNode(), 'a') ? 0 : -1; + }, + exec: function () { + var anchor = closest(this.currentNode(), 'a'); + + if (anchor) { + while (anchor.firstChild) { + insertBefore(anchor.firstChild, anchor); + } + + remove(anchor); + } + }, + tooltip: 'Unlink' + }, + // END_COMMAND + + + // START_COMMAND: Quote + quote: { + exec: function (caller, html, author) { + var before = '
    ', + end = '
    '; + + // if there is HTML passed set end to null so any selected + // text is replaced + if (html) { + author = (author ? '' + author + '' : ''); + before = before + author + html + end; + end = null; + // if not add a newline to the end of the inserted quote + } else if (this.getRangeHelper().selectedHtml() === '') { + end = (IE_BR_FIX ? '' : '
    ') + end; + } + + this.wysiwygEditorInsertHtml(before, end); + }, + tooltip: 'Insert a Quote' + }, + // END_COMMAND + + // START_COMMAND: Emoticons + emoticon: { + exec: function (caller) { + var editor = this; + + var createContent = function (includeMore) { + var moreLink, + opts = editor.opts, + emoticonsRoot = opts.emoticonsRoot || '', + emoticonsCompat = opts.emoticonsCompat, + rangeHelper = editor.getRangeHelper(), + startSpace = emoticonsCompat && + rangeHelper.getOuterText(true, 1) !== ' ' ? ' ' : '', + endSpace = emoticonsCompat && + rangeHelper.getOuterText(false, 1) !== ' ' ? ' ' : '', + content = createElement('div'), + line = createElement('div'), + perLine = 0, + emoticons = extend( + {}, + opts.emoticons.dropdown, + includeMore ? opts.emoticons.more : {} + ); + + appendChild(content, line); + + perLine = Math.sqrt(Object.keys(emoticons).length); + + on(content, 'click', 'img', function (e) { + editor.insert(startSpace + attr(this, 'alt') + endSpace, + null, false).closeDropDown(true); + + e.preventDefault(); + }); + + each(emoticons, function (code, emoticon) { + appendChild(line, createElement('img', { + src: emoticonsRoot + (emoticon.url || emoticon), + alt: code, + title: emoticon.tooltip || code + })); + + if (line.children.length >= perLine) { + line = createElement('div'); + appendChild(content, line); + } + }); + + if (!includeMore && opts.emoticons.more) { + moreLink = createElement('a', { + className: 'sceditor-more' + }); + + appendChild(moreLink, + document.createTextNode(editor._('More'))); + + on(moreLink, 'click', function (e) { + editor.createDropDown( + caller, 'more-emoticons', createContent(true) + ); + + e.preventDefault(); + }); + + appendChild(content, moreLink); + } + + return content; + }; + + editor.createDropDown(caller, 'emoticons', createContent(false)); + }, + txtExec: function (caller) { + defaultCmds.emoticon.exec.call(this, caller); + }, + tooltip: 'Insert an emoticon' + }, + // END_COMMAND + + // START_COMMAND: YouTube + youtube: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + appendChild(content, _tmpl('youtubeMenu', { + label: editor._('Video URL:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var val = find(content, '#link')[0].value; + var idMatch = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/); + var timeMatch = val.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/); + var time = 0; + + if (timeMatch) { + each(timeMatch[1].split(/[hms]/), function (i, val) { + if (val !== '') { + time = (time * 60) + Number(val); + } + }); + } + + if (idMatch && /^[a-zA-Z0-9_\-]{11}$/.test(idMatch[1])) { + callback(idMatch[1], time); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (btn) { + var editor = this; + + defaultCmds.youtube._dropDown(editor, btn, function (id, time) { + editor.wysiwygEditorInsertHtml(_tmpl('youtube', { + id: id, + time: time + })); + }); + }, + tooltip: 'Insert a YouTube video' + }, + // END_COMMAND + + // START_COMMAND: Date + date: { + _date: function (editor) { + var now = new Date(), + year = now.getYear(), + month = now.getMonth() + 1, + day = now.getDate(); + + if (year < 2000) { + year = 1900 + year; + } + + if (month < 10) { + month = '0' + month; + } + + if (day < 10) { + day = '0' + day; + } + + return editor.opts.dateFormat + .replace(/year/i, year) + .replace(/month/i, month) + .replace(/day/i, day); + }, + exec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + txtExec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + tooltip: 'Insert current date' + }, + // END_COMMAND + + // START_COMMAND: Time + time: { + _time: function () { + var now = new Date(), + hours = now.getHours(), + mins = now.getMinutes(), + secs = now.getSeconds(); + + if (hours < 10) { + hours = '0' + hours; + } + + if (mins < 10) { + mins = '0' + mins; + } + + if (secs < 10) { + secs = '0' + secs; + } + + return hours + ':' + mins + ':' + secs; + }, + exec: function () { + this.insertText(defaultCmds.time._time()); + }, + txtExec: function () { + this.insertText(defaultCmds.time._time()); + }, + tooltip: 'Insert current time' + }, + // END_COMMAND + + + // START_COMMAND: Ltr + ltr: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'ltr'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'ltr' ? '' : 'ltr'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Left-to-Right' + }, + // END_COMMAND + + // START_COMMAND: Rtl + rtl: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'rtl'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'rtl' ? '' : 'rtl'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Right-to-Left' + }, + // END_COMMAND + + + // START_COMMAND: Print + print: { + exec: 'print', + tooltip: 'Print' + }, + // END_COMMAND + + // START_COMMAND: Maximize + maximize: { + state: function () { + return this.maximize(); + }, + exec: function () { + this.maximize(!this.maximize()); + }, + txtExec: function () { + this.maximize(!this.maximize()); + }, + tooltip: 'Maximize', + shortcut: 'Ctrl+Shift+M' + }, + // END_COMMAND + + // START_COMMAND: Source + source: { + state: function () { + return this.sourceMode(); + }, + exec: function () { + this.toggleSourceMode(); + }, + txtExec: function () { + this.toggleSourceMode(); + }, + tooltip: 'View source', + shortcut: 'Ctrl+Shift+S' + }, + // END_COMMAND + + // this is here so that commands above can be removed + // without having to remove the , after the last one. + // Needed for IE. + ignore: {} + }; + + var plugins = {}; + + /** + * Plugin Manager class + * @class PluginManager + * @name PluginManager + */ + function PluginManager(thisObj) { + /** + * Alias of this + * + * @private + * @type {Object} + */ + var base = this; + + /** + * Array of all currently registered plugins + * + * @type {Array} + * @private + */ + var registeredPlugins = []; + + + /** + * Changes a signals name from "name" into "signalName". + * + * @param {string} signal + * @return {string} + * @private + */ + var formatSignalName = function (signal) { + return 'signal' + signal.charAt(0).toUpperCase() + signal.slice(1); + }; + + /** + * Calls handlers for a signal + * + * @see call() + * @see callOnlyFirst() + * @param {Array} args + * @param {boolean} returnAtFirst + * @return {*} + * @private + */ + var callHandlers = function (args, returnAtFirst) { + args = [].slice.call(args); + + var idx, ret, + signal = formatSignalName(args.shift()); + + for (idx = 0; idx < registeredPlugins.length; idx++) { + if (signal in registeredPlugins[idx]) { + ret = registeredPlugins[idx][signal].apply(thisObj, args); + + if (returnAtFirst) { + return ret; + } + } + } + }; + + /** + * Calls all handlers for the passed signal + * + * @param {string} signal + * @param {...string} args + * @function + * @name call + * @memberOf PluginManager.prototype + */ + base.call = function () { + callHandlers(arguments, false); + }; + + /** + * Calls the first handler for a signal, and returns the + * + * @param {string} signal + * @param {...string} args + * @return {*} The result of calling the handler + * @function + * @name callOnlyFirst + * @memberOf PluginManager.prototype + */ + base.callOnlyFirst = function () { + return callHandlers(arguments, true); + }; + + /** + * Checks if a signal has a handler + * + * @param {string} signal + * @return {boolean} + * @function + * @name hasHandler + * @memberOf PluginManager.prototype + */ + base.hasHandler = function (signal) { + var i = registeredPlugins.length; + signal = formatSignalName(signal); + + while (i--) { + if (signal in registeredPlugins[i]) { + return true; + } + } + + return false; + }; + + /** + * Checks if the plugin exists in plugins + * + * @param {string} plugin + * @return {boolean} + * @function + * @name exists + * @memberOf PluginManager.prototype + */ + base.exists = function (plugin) { + if (plugin in plugins) { + plugin = plugins[plugin]; + + return typeof plugin === 'function' && + typeof plugin.prototype === 'object'; + } + + return false; + }; + + /** + * Checks if the passed plugin is currently registered. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name isRegistered + * @memberOf PluginManager.prototype + */ + base.isRegistered = function (plugin) { + if (base.exists(plugin)) { + var idx = registeredPlugins.length; + + while (idx--) { + if (registeredPlugins[idx] instanceof plugins[plugin]) { + return true; + } + } + } + + return false; + }; + + /** + * Registers a plugin to receive signals + * + * @param {string} plugin + * @return {boolean} + * @function + * @name register + * @memberOf PluginManager.prototype + */ + base.register = function (plugin) { + if (!base.exists(plugin) || base.isRegistered(plugin)) { + return false; + } + + plugin = new plugins[plugin](); + registeredPlugins.push(plugin); + + if ('init' in plugin) { + plugin.init.call(thisObj); + } + + return true; + }; + + /** + * Deregisters a plugin. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name deregister + * @memberOf PluginManager.prototype + */ + base.deregister = function (plugin) { + var removedPlugin, + pluginIdx = registeredPlugins.length, + removed = false; + + if (!base.isRegistered(plugin)) { + return removed; + } + + while (pluginIdx--) { + if (registeredPlugins[pluginIdx] instanceof plugins[plugin]) { + removedPlugin = registeredPlugins.splice(pluginIdx, 1)[0]; + removed = true; + + if ('destroy' in removedPlugin) { + removedPlugin.destroy.call(thisObj); + } + } + } + + return removed; + }; + + /** + * Clears all plugins and removes the owner reference. + * + * Calling any functions on this object after calling + * destroy will cause a JS error. + * + * @name destroy + * @memberOf PluginManager.prototype + */ + base.destroy = function () { + var i = registeredPlugins.length; + + while (i--) { + if ('destroy' in registeredPlugins[i]) { + registeredPlugins[i].destroy.call(thisObj); + } + } + + registeredPlugins = []; + thisObj = null; + }; + } + + PluginManager.plugins = plugins; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$1 = ie && ie < 11; + + + /** + * Gets the text, start/end node and offset for + * length chars left or right of the passed node + * at the specified offset. + * + * @param {Node} node + * @param {number} offset + * @param {boolean} isLeft + * @param {number} length + * @return {Object} + * @private + */ + var outerText = function (range, isLeft, length) { + var nodeValue, remaining, start, end, node, + text = '', + next = range.startContainer, + offset = range.startOffset; + + // Handle cases where node is a paragraph and offset + // refers to the index of a text node. + // 3 = text node + if (next && next.nodeType !== 3) { + next = next.childNodes[offset]; + offset = 0; + } + + start = end = offset; + + while (length > text.length && next && next.nodeType === 3) { + nodeValue = next.nodeValue; + remaining = length - text.length; + + // If not the first node, start and end should be at their + // max values as will be updated when getting the text + if (node) { + end = nodeValue.length; + start = 0; + } + + node = next; + + if (isLeft) { + start = Math.max(end - remaining, 0); + offset = start; + + text = nodeValue.substr(start, end - start) + text; + next = node.previousSibling; + } else { + end = Math.min(remaining, nodeValue.length); + offset = start + end; + + text += nodeValue.substr(start, end); + next = node.nextSibling; + } + } + + return { + node: node || next, + offset: offset, + text: text + }; + }; + + /** + * Range helper + * + * @class RangeHelper + * @name RangeHelper + */ + function RangeHelper(win, d) { + var _createMarker, _prepareInput, + doc = d || win.contentDocument || win.document, + startMarker = 'sceditor-start-marker', + endMarker = 'sceditor-end-marker', + base = this; + + /** + * Inserts HTML into the current range replacing any selected + * text. + * + * If endHTML is specified the selected contents will be put between + * html and endHTML. If there is nothing selected html and endHTML are + * just concatenate together. + * + * @param {string} html + * @param {string} [endHTML] + * @return False on fail + * @function + * @name insertHTML + * @memberOf RangeHelper.prototype + */ + base.insertHTML = function (html, endHTML) { + var node, div, + range = base.selectedRange(); + + if (!range) { + return false; + } + + if (endHTML) { + html += base.selectedHtml() + endHTML; + } + + div = createElement('p', {}, doc); + node = doc.createDocumentFragment(); + div.innerHTML = html; + + while (div.firstChild) { + appendChild(node, div.firstChild); + } + + base.insertNode(node); + }; + + /** + * Prepares HTML to be inserted by adding a zero width space + * if the last child is empty and adding the range start/end + * markers to the last child. + * + * @param {Node|string} node + * @param {Node|string} [endNode] + * @param {boolean} [returnHtml] + * @return {Node|string} + * @private + */ + _prepareInput = function (node, endNode, returnHtml) { + var lastChild, + frag = doc.createDocumentFragment(); + + if (typeof node === 'string') { + if (endNode) { + node += base.selectedHtml() + endNode; + } + + frag = parseHTML(node); + } else { + appendChild(frag, node); + + if (endNode) { + appendChild(frag, base.selectedRange().extractContents()); + appendChild(frag, endNode); + } + } + + if (!(lastChild = frag.lastChild)) { + return; + } + + while (!isInline(lastChild.lastChild, true)) { + lastChild = lastChild.lastChild; + } + + if (canHaveChildren(lastChild)) { + // Webkit won't allow the cursor to be placed inside an + // empty tag, so add a zero width space to it. + if (!lastChild.lastChild) { + appendChild(lastChild, document.createTextNode('\u200B')); + } + } else { + lastChild = frag; + } + + base.removeMarkers(); + + // Append marks to last child so when restored cursor will be in + // the right place + appendChild(lastChild, _createMarker(startMarker)); + appendChild(lastChild, _createMarker(endMarker)); + + if (returnHtml) { + var div = createElement('div'); + appendChild(div, frag); + + return div.innerHTML; + } + + return frag; + }; + + /** + * The same as insertHTML except with DOM nodes instead + * + * Warning: the nodes must belong to the + * document they are being inserted into. Some browsers + * will throw exceptions if they don't. + * + * Returns boolean false on fail + * + * @param {Node} node + * @param {Node} endNode + * @return {false|undefined} + * @function + * @name insertNode + * @memberOf RangeHelper.prototype + */ + base.insertNode = function (node, endNode) { + var input = _prepareInput(node, endNode), + range = base.selectedRange(), + parent$$1 = range.commonAncestorContainer; + + if (!input) { + return false; + } + + range.deleteContents(); + + // FF allows
    to be selected but inserting a node + // into
    will cause it not to be displayed so must + // insert before the
    in FF. + // 3 = TextNode + if (parent$$1 && parent$$1.nodeType !== 3 && !canHaveChildren(parent$$1)) { + insertBefore(input, parent$$1); + } else { + range.insertNode(input); + } + + base.restoreRange(); + }; + + /** + * Clones the selected Range + * + * @return {Range} + * @function + * @name cloneSelected + * @memberOf RangeHelper.prototype + */ + base.cloneSelected = function () { + var range = base.selectedRange(); + + if (range) { + return range.cloneRange(); + } + }; + + /** + * Gets the selected Range + * + * @return {Range} + * @function + * @name selectedRange + * @memberOf RangeHelper.prototype + */ + base.selectedRange = function () { + var range, firstChild, + sel = win.getSelection(); + + if (!sel) { + return; + } + + // When creating a new range, set the start to the first child + // element of the body element to avoid errors in FF. + if (sel.rangeCount <= 0) { + firstChild = doc.body; + while (firstChild.firstChild) { + firstChild = firstChild.firstChild; + } + + range = doc.createRange(); + // Must be setStartBefore otherwise it can cause infinite + // loops with lists in WebKit. See issue 442 + range.setStartBefore(firstChild); + + sel.addRange(range); + } + + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } + + return range; + }; + + /** + * Gets if there is currently a selection + * + * @return {boolean} + * @function + * @name hasSelection + * @since 1.4.4 + * @memberOf RangeHelper.prototype + */ + base.hasSelection = function () { + var sel = win.getSelection(); + + return sel && sel.rangeCount > 0; + }; + + /** + * Gets the currently selected HTML + * + * @return {string} + * @function + * @name selectedHtml + * @memberOf RangeHelper.prototype + */ + base.selectedHtml = function () { + var div, + range = base.selectedRange(); + + if (range) { + div = createElement('p', {}, doc); + appendChild(div, range.cloneContents()); + + return div.innerHTML; + } + + return ''; + }; + + /** + * Gets the parent node of the selected contents in the range + * + * @return {HTMLElement} + * @function + * @name parentNode + * @memberOf RangeHelper.prototype + */ + base.parentNode = function () { + var range = base.selectedRange(); + + if (range) { + return range.commonAncestorContainer; + } + }; + + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @return {HTMLElement} + * @function + * @name getFirstBlockParent + * @memberOf RangeHelper.prototype + */ + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @param {Node} [n] The element to get the first block level parent from + * @return {HTMLElement} + * @function + * @name getFirstBlockParent^2 + * @since 1.4.1 + * @memberOf RangeHelper.prototype + */ + base.getFirstBlockParent = function (node) { + var func = function (elm) { + if (!isInline(elm, true)) { + return elm; + } + + elm = elm ? elm.parentNode : null; + + return elm ? func(elm) : elm; + }; + + return func(node || base.parentNode()); + }; + + /** + * Inserts a node at either the start or end of the current selection + * + * @param {Bool} start + * @param {Node} node + * @function + * @name insertNodeAt + * @memberOf RangeHelper.prototype + */ + base.insertNodeAt = function (start, node) { + var currentRange = base.selectedRange(), + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(start); + range.insertNode(node); + + // Reselect the current range. + // Fixes issue with Chrome losing the selection. Issue#82 + base.selectRange(currentRange); + }; + + /** + * Creates a marker node + * + * @param {string} id + * @return {HTMLSpanElement} + * @private + */ + _createMarker = function (id) { + base.removeMarker(id); + + var marker = createElement('span', { + id: id, + className: 'sceditor-selection sceditor-ignore', + style: 'display:none;line-height:0' + }, doc); + + marker.innerHTML = ' '; + + return marker; + }; + + /** + * Inserts start/end markers for the current selection + * which can be used by restoreRange to re-select the + * range. + * + * @memberOf RangeHelper.prototype + * @function + * @name insertMarkers + */ + base.insertMarkers = function () { + var currentRange = base.selectedRange(); + var startNode = _createMarker(startMarker); + + base.removeMarkers(); + base.insertNodeAt(true, startNode); + + // Fixes issue with end marker sometimes being placed before + // the start marker when the range is collapsed. + if (currentRange && currentRange.collapsed) { + startNode.parentNode.insertBefore( + _createMarker(endMarker), startNode.nextSibling); + } else { + base.insertNodeAt(false, _createMarker(endMarker)); + } + }; + + /** + * Gets the marker with the specified ID + * + * @param {string} id + * @return {Node} + * @function + * @name getMarker + * @memberOf RangeHelper.prototype + */ + base.getMarker = function (id) { + return doc.getElementById(id); + }; + + /** + * Removes the marker with the specified ID + * + * @param {string} id + * @function + * @name removeMarker + * @memberOf RangeHelper.prototype + */ + base.removeMarker = function (id) { + var marker = base.getMarker(id); + + if (marker) { + remove(marker); + } + }; + + /** + * Removes the start/end markers + * + * @function + * @name removeMarkers + * @memberOf RangeHelper.prototype + */ + base.removeMarkers = function () { + base.removeMarker(startMarker); + base.removeMarker(endMarker); + }; + + /** + * Saves the current range location. Alias of insertMarkers() + * + * @function + * @name saveRage + * @memberOf RangeHelper.prototype + */ + base.saveRange = function () { + base.insertMarkers(); + }; + + /** + * Select the specified range + * + * @param {Range} range + * @function + * @name selectRange + * @memberOf RangeHelper.prototype + */ + base.selectRange = function (range) { + var lastChild; + var sel = win.getSelection(); + var container = range.endContainer; + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$1 && range.collapsed && container && + !isInline(container, true)) { + + lastChild = container.lastChild; + while (lastChild && is(lastChild, '.sceditor-ignore')) { + lastChild = lastChild.previousSibling; + } + + if (is(lastChild, 'br')) { + var rng = doc.createRange(); + rng.setEndAfter(lastChild); + rng.collapse(false); + + if (base.compare(range, rng)) { + range.setStartBefore(lastChild); + range.collapse(true); + } + } + } + + if (sel) { + base.clear(); + sel.addRange(range); + } + }; + + /** + * Restores the last range saved by saveRange() or insertMarkers() + * + * @function + * @name restoreRange + * @memberOf RangeHelper.prototype + */ + base.restoreRange = function () { + var isCollapsed, + range = base.selectedRange(), + start = base.getMarker(startMarker), + end = base.getMarker(endMarker); + + if (!start || !end || !range) { + return false; + } + + isCollapsed = start.nextSibling === end; + + range = doc.createRange(); + range.setStartBefore(start); + range.setEndAfter(end); + + if (isCollapsed) { + range.collapse(true); + } + + base.selectRange(range); + base.removeMarkers(); + }; + + /** + * Selects the text left and right of the current selection + * + * @param {number} left + * @param {number} right + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.selectOuterText = function (left, right) { + var start, end, + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(false); + + start = outerText(range, true, left); + end = outerText(range, false, right); + + range.setStart(start.node, start.offset); + range.setEnd(end.node, end.offset); + + base.selectRange(range); + }; + + /** + * Gets the text left or right of the current selection + * + * @param {boolean} before + * @param {number} length + * @return {string} + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.getOuterText = function (before, length) { + var range = base.cloneSelected(); + + if (!range) { + return ''; + } + + range.collapse(!before); + + return outerText(range, before, length).text; + }; + + /** + * Replaces keywords with values based on the current caret position + * + * @param {Array} keywords + * @param {boolean} includeAfter If to include the text after the + * current caret position or just + * text before + * @param {boolean} keywordsSorted If the keywords array is pre + * sorted shortest to longest + * @param {number} longestKeyword Length of the longest keyword + * @param {boolean} requireWhitespace If the key must be surrounded + * by whitespace + * @param {string} keypressChar If this is being called from + * a keypress event, this should be + * set to the pressed character + * @return {boolean} + * @function + * @name replaceKeyword + * @memberOf RangeHelper.prototype + */ + // eslint-disable-next-line max-params + base.replaceKeyword = function ( + keywords, + includeAfter, + keywordsSorted, + longestKeyword, + requireWhitespace, + keypressChar + ) { + if (!keywordsSorted) { + keywords.sort(function (a, b) { + return a[0].length - b[0].length; + }); + } + + var outerText, match, matchPos, startIndex, + leftLen, charsLeft, keyword, keywordLen, + whitespaceRegex = '(^|[\\s\xA0\u2002\u2003\u2009])', + keywordIdx = keywords.length, + whitespaceLen = requireWhitespace ? 1 : 0, + maxKeyLen = longestKeyword || + keywords[keywordIdx - 1][0].length; + + if (requireWhitespace) { + maxKeyLen++; + } + + keypressChar = keypressChar || ''; + outerText = base.getOuterText(true, maxKeyLen); + leftLen = outerText.length; + outerText += keypressChar; + + if (includeAfter) { + outerText += base.getOuterText(false, maxKeyLen); + } + + while (keywordIdx--) { + keyword = keywords[keywordIdx][0]; + keywordLen = keyword.length; + startIndex = Math.max(0, leftLen - keywordLen - whitespaceLen); + matchPos = -1; + + if (requireWhitespace) { + match = outerText + .substr(startIndex) + .match(new RegExp(whitespaceRegex + + regex(keyword) + whitespaceRegex)); + + if (match) { + // Add the length of the text that was removed by + // substr() and also add 1 for the whitespace + matchPos = match.index + startIndex + match[1].length; + } + } else { + matchPos = outerText.indexOf(keyword, startIndex); + } + + if (matchPos > -1) { + // Make sure the match is between before and + // after, not just entirely in one side or the other + if (matchPos <= leftLen && + matchPos + keywordLen + whitespaceLen >= leftLen) { + charsLeft = leftLen - matchPos; + + // If the keypress char is white space then it should + // not be replaced, only chars that are part of the + // key should be replaced. + base.selectOuterText( + charsLeft, + keywordLen - charsLeft - + (/^\S/.test(keypressChar) ? 1 : 0) + ); + + base.insertHTML(keywords[keywordIdx][1]); + return true; + } + } + } + + return false; + }; + + /** + * Compares two ranges. + * + * If rangeB is undefined it will be set to + * the current selected range + * + * @param {Range} rngA + * @param {Range} [rngB] + * @return {boolean} + * @function + * @name compare + * @memberOf RangeHelper.prototype + */ + base.compare = function (rngA, rngB) { + if (!rngB) { + rngB = base.selectedRange(); + } + + if (!rngA || !rngB) { + return !rngA && !rngB; + } + + return rngA.compareBoundaryPoints(Range.END_TO_END, rngB) === 0 && + rngA.compareBoundaryPoints(Range.START_TO_START, rngB) === 0; + }; + + /** + * Removes any current selection + * + * @since 1.4.6 + * @function + * @name clear + * @memberOf RangeHelper.prototype + */ + base.clear = function () { + var sel = win.getSelection(); + + if (sel) { + if (sel.removeAllRanges) { + sel.removeAllRanges(); + } else if (sel.empty) { + sel.empty(); + } + } + }; + } + + /** + * Checks all emoticons are surrounded by whitespace and + * replaces any that aren't with with their emoticon code. + * + * @param {HTMLElement} node + * @param {rangeHelper} rangeHelper + * @return {void} + */ + function checkWhitespace(node, rangeHelper) { + var noneWsRegex = /[^\s\xA0\u2002\u2003\u2009\u00a0]+/; + var emoticons = node && find(node, 'img[data-sceditor-emoticon]'); + + if (!node || !emoticons.length) { + return; + } + + for (var i = 0; i < emoticons.length; i++) { + var emoticon = emoticons[i]; + var parent$$1 = emoticon.parentNode; + var prev = emoticon.previousSibling; + var next = emoticon.nextSibling; + + if ((!prev || !noneWsRegex.test(prev.nodeValue.slice(-1))) && + (!next || !noneWsRegex.test((next.nodeValue || '')[0]))) { + continue; + } + + var range = rangeHelper.cloneSelected(); + var rangeStart = -1; + var rangeStartContainer = range.startContainer; + var previousText = prev.nodeValue; + + // For IE's HTMLPhraseElement + if (previousText === null) { + previousText = prev.innerText || ''; + } + + previousText += data(emoticon, 'sceditor-emoticon'); + + // If the cursor is after the removed emoticon, add + // the length of the newly added text to it + if (rangeStartContainer === next) { + rangeStart = previousText.length + range.startOffset; + } + + // If the cursor is set before the next node, set it to + // the end of the new text node + if (rangeStartContainer === node && + node.childNodes[range.startOffset] === next) { + rangeStart = previousText.length; + } + + // If the cursor is set before the removed emoticon, + // just keep it at that position + if (rangeStartContainer === prev) { + rangeStart = range.startOffset; + } + + if (!next || next.nodeType !== TEXT_NODE) { + next = parent$$1.insertBefore( + parent$$1.ownerDocument.createTextNode(''), next + ); + } + + next.insertData(0, previousText); + remove(prev); + remove(emoticon); + + // Need to update the range starting position if it's been modified + if (rangeStart > -1) { + range.setStart(next, rangeStart); + range.collapse(true); + rangeHelper.selectRange(range); + } + } + } + + /** + * Replaces any emoticons inside the root node with images. + * + * emoticons should be an object where the key is the emoticon + * code and the value is the HTML to replace it with. + * + * @param {HTMLElement} root + * @param {Object} emoticons + * @param {boolean} emoticonsCompat + * @return {void} + */ + function replace(root, emoticons, emoticonsCompat) { + var doc = root.ownerDocument; + var space = '(^|\\s|\xA0|\u2002|\u2003|\u2009|$)'; + var emoticonCodes = []; + var emoticonRegex = {}; + + // TODO: Make this tag configurable. + if (parent(root, 'code')) { + return; + } + + each(emoticons, function (key) { + emoticonRegex[key] = new RegExp(space + regex(key) + space); + emoticonCodes.push(key); + }); + + // Sort keys longest to shortest so that longer keys + // take precedence (avoids bugs with shorter keys partially + // matching longer ones) + emoticonCodes.sort(function (a, b) { + return b.length - a.length; + }); + + (function convert(node) { + node = node.firstChild; + + while (node) { + // TODO: Make this tag configurable. + if (node.nodeType === ELEMENT_NODE && !is(node, 'code')) { + convert(node); + } + + if (node.nodeType === TEXT_NODE) { + for (var i = 0; i < emoticonCodes.length; i++) { + var text = node.nodeValue; + var key = emoticonCodes[i]; + var index = emoticonsCompat ? + text.search(emoticonRegex[key]) : + text.indexOf(key); + + if (index > -1) { + // When emoticonsCompat is enabled this will be the + // position after any white space + var startIndex = text.indexOf(key, index); + var fragment = parseHTML(emoticons[key], doc); + var after = text.substr(startIndex + key.length); + + fragment.appendChild(doc.createTextNode(after)); + + node.nodeValue = text.substr(0, startIndex); + node.parentNode + .insertBefore(fragment, node.nextSibling); + } + } + } + + node = node.nextSibling; + } + }(root)); + } + + var globalWin = window; + var globalDoc = document; + + var IE_VER = ie; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$2 = IE_VER && IE_VER < 11; + + var IMAGE_MIME_REGEX = /^image\/(p?jpe?g|gif|png|bmp)$/i; + + /** + * Wrap inlines that are in the root in paragraphs. + * + * @param {HTMLBodyElement} body + * @param {Document} doc + * @private + */ + function wrapInlines(body, doc) { + var wrapper; + + traverse(body, function (node) { + if (isInline(node, true)) { + if (!wrapper) { + wrapper = createElement('p', {}, doc); + insertBefore(wrapper, node); + } + + if (node.nodeType !== TEXT_NODE || node.nodeValue !== '') { + appendChild(wrapper, node); + } + } else { + wrapper = null; + } + }, false, true); + } + + /** + * SCEditor - A lightweight WYSIWYG editor + * + * @param {HTMLTextAreaElement} original The textarea to be converted + * @param {Object} userOptions + * @class SCEditor + * @name SCEditor + */ + function SCEditor(original, userOptions) { + /** + * Alias of this + * + * @private + */ + var base = this; + + /** + * Editor format like BBCode or HTML + */ + var format; + + /** + * The div which contains the editor and toolbar + * + * @type {HTMLDivElement} + * @private + */ + var editorContainer; + + /** + * Map of events handlers bound to this instance. + * + * @type {Object} + * @private + */ + var eventHandlers = {}; + + /** + * The editors toolbar + * + * @type {HTMLDivElement} + * @private + */ + var toolbar; + + /** + * The editors iframe which should be in design mode + * + * @type {HTMLIFrameElement} + * @private + */ + var wysiwygEditor; + + /** + * The editors window + * + * @type {Window} + * @private + */ + var wysiwygWindow; + + /** + * The WYSIWYG editors body element + * + * @type {HTMLBodyElement} + * @private + */ + var wysiwygBody; + + /** + * The WYSIWYG editors document + * + * @type {Document} + * @private + */ + var wysiwygDocument; + + /** + * The editors textarea for viewing source + * + * @type {HTMLTextAreaElement} + * @private + */ + var sourceEditor; + + /** + * The current dropdown + * + * @type {HTMLDivElement} + * @private + */ + var dropdown; + + /** + * Store the last cursor position. Needed for IE because it forgets + * + * @type {Range} + * @private + */ + var lastRange; + + /** + * If the user is currently composing text via IME + * @type {boolean} + */ + var isComposing; + + /** + * Timer for valueChanged key handler + * @type {number} + */ + var valueChangedKeyUpTimer; + + /** + * The editors locale + * + * @private + */ + var locale; + + /** + * Stores a cache of preloaded images + * + * @private + * @type {Array.} + */ + var preLoadCache = []; + + /** + * The editors rangeHelper instance + * + * @type {RangeHelper} + * @private + */ + var rangeHelper; + + /** + * An array of button state handlers + * + * @type {Array.} + * @private + */ + var btnStateHandlers = []; + + /** + * Plugin manager instance + * + * @type {PluginManager} + * @private + */ + var pluginManager; + + /** + * The current node containing the selection/caret + * + * @type {Node} + * @private + */ + var currentNode; + + /** + * The first block level parent of the current node + * + * @type {node} + * @private + */ + var currentBlockNode; + + /** + * The current node selection/caret + * + * @type {Object} + * @private + */ + var currentSelection; + + /** + * Used to make sure only 1 selection changed + * check is called every 100ms. + * + * Helps improve performance as it is checked a lot. + * + * @type {boolean} + * @private + */ + var isSelectionCheckPending; + + /** + * If content is required (equivalent to the HTML5 required attribute) + * + * @type {boolean} + * @private + */ + var isRequired; + + /** + * The inline CSS style element. Will be undefined + * until css() is called for the first time. + * + * @type {HTMLStyleElement} + * @private + */ + var inlineCss; + + /** + * Object containing a list of shortcut handlers + * + * @type {Object} + * @private + */ + var shortcutHandlers = {}; + + /** + * The min and max heights that autoExpand should stay within + * + * @type {Object} + * @private + */ + var autoExpandBounds; + + /** + * Timeout for the autoExpand function to throttle calls + * + * @private + */ + var autoExpandThrottle; + + /** + * Cache of the current toolbar buttons + * + * @type {Object} + * @private + */ + var toolbarButtons = {}; + + /** + * Last scroll position before maximizing so + * it can be restored when finished. + * + * @type {number} + * @private + */ + var maximizeScrollPosition; + + /** + * Stores the contents while a paste is taking place. + * + * Needed to support browsers that lack clipboard API support. + * + * @type {?DocumentFragment} + * @private + */ + var pasteContentFragment; + + /** + * All the emoticons from dropdown, more and hidden combined + * and with the emoticons root set + * + * @type {!Object} + * @private + */ + var allEmoticons = {}; + + /** + * Current icon set if any + * + * @type {?Object} + * @private + */ + var icons; + + /** + * Private functions + * @private + */ + var init, + replaceEmoticons, + handleCommand, + saveRange, + initEditor, + initPlugins, + initLocale, + initToolBar, + initOptions, + initEvents, + initResize, + initEmoticons, + handlePasteEvt, + handlePasteData, + handleKeyDown, + handleBackSpace, + handleKeyPress, + handleFormReset, + handleMouseDown, + handleComposition, + handleEvent, + handleDocumentClick, + updateToolBar, + updateActiveButtons, + sourceEditorSelectedText, + appendNewLine, + checkSelectionChanged, + checkNodeChanged, + autofocus, + emoticonsKeyPress, + emoticonsCheckWhitespace, + currentStyledBlockNode, + triggerValueChanged, + valueChangedBlur, + valueChangedKeyUp, + autoUpdate, + autoExpand; + + /** + * All the commands supported by the editor + * @name commands + * @memberOf SCEditor.prototype + */ + base.commands = extend(true, {}, (userOptions.commands || defaultCmds)); + + /** + * Options for this editor instance + * @name opts + * @memberOf SCEditor.prototype + */ + var options = base.opts = extend( + true, {}, defaultOptions, userOptions + ); + + // Don't deep extend emoticons (fixes #565) + base.opts.emoticons = userOptions.emoticons || defaultOptions.emoticons; + + /** + * Creates the editor iframe and textarea + * @private + */ + init = function () { + original._sceditor = base; + + // Load locale + if (options.locale && options.locale !== 'en') { + initLocale(); + } + + editorContainer = createElement('div', { + className: 'sceditor-container' + }); + + insertBefore(editorContainer, original); + css(editorContainer, 'z-index', options.zIndex); + + // Add IE version to the container to allow IE specific CSS + // fixes without using CSS hacks or conditional comments + if (IE_VER) { + addClass(editorContainer, 'ie ie' + IE_VER); + } + + isRequired = original.required; + original.required = false; + + var FormatCtor = SCEditor.formats[options.format]; + format = FormatCtor ? new FormatCtor() : {}; + if ('init' in format) { + format.init.call(base); + } + + // create the editor + initPlugins(); + initEmoticons(); + initToolBar(); + initEditor(); + initOptions(); + initEvents(); + + // force into source mode if is a browser that can't handle + // full editing + if (!isWysiwygSupported) { + base.toggleSourceMode(); + } + + updateActiveButtons(); + + var loaded = function () { + off(globalWin, 'load', loaded); + + if (options.autofocus) { + autofocus(); + } + + autoExpand(); + appendNewLine(); + // TODO: use editor doc and window? + pluginManager.call('ready'); + if ('onReady' in format) { + format.onReady.call(base); + } + }; + on(globalWin, 'load', loaded); + if (globalDoc.readyState === 'complete') { + loaded(); + } + }; + + initPlugins = function () { + var plugins = options.plugins; + + plugins = plugins ? plugins.toString().split(',') : []; + pluginManager = new PluginManager(base); + + plugins.forEach(function (plugin) { + pluginManager.register(plugin.trim()); + }); + }; + + /** + * Init the locale variable with the specified locale if possible + * @private + * @return void + */ + initLocale = function () { + var lang; + + locale = SCEditor.locale[options.locale]; + + if (!locale) { + lang = options.locale.split('-'); + locale = SCEditor.locale[lang[0]]; + } + + // Locale DateTime format overrides any specified in the options + if (locale && locale.dateFormat) { + options.dateFormat = locale.dateFormat; + } + }; + + /** + * Creates the editor iframe and textarea + * @private + */ + initEditor = function () { + sourceEditor = createElement('textarea'); + wysiwygEditor = createElement('iframe', { + frameborder: 0, + allowfullscreen: true + }); + + /* This needs to be done right after they are created because, + * for any reason, the user may not want the value to be tinkered + * by any filters. + */ + if (options.startInSourceMode) { + addClass(editorContainer, 'sourceMode'); + hide(wysiwygEditor); + } else { + addClass(editorContainer, 'wysiwygMode'); + hide(sourceEditor); + } + + if (!options.spellcheck) { + attr(editorContainer, 'spellcheck', 'false'); + } + + if (globalWin.location.protocol === 'https:') { + // eslint-disable-next-line no-script-url + attr(wysiwygEditor, 'src', 'javascript:false'); + } + + // Add the editor to the container + appendChild(editorContainer, wysiwygEditor); + appendChild(editorContainer, sourceEditor); + + // TODO: make this optional somehow + base.dimensions( + options.width || width(original), + options.height || height(original) + ); + + // Add IE version class to the HTML element so can apply + // conditional styling without CSS hacks + var className = IE_VER ? 'ie ie' + IE_VER : ''; + // Add ios to HTML so can apply CSS fix to only it + className += ios ? ' ios' : ''; + + wysiwygDocument = wysiwygEditor.contentDocument; + wysiwygDocument.open(); + wysiwygDocument.write(_tmpl('html', { + attrs: ' class="' + className + '"', + spellcheck: options.spellcheck ? '' : 'spellcheck="false"', + charset: options.charset, + style: options.style + })); + wysiwygDocument.close(); + + wysiwygBody = wysiwygDocument.body; + wysiwygWindow = wysiwygEditor.contentWindow; + + base.readOnly(!!options.readOnly); + + // iframe overflow fix for iOS, also fixes an IE issue with the + // editor not getting focus when clicking inside + if (ios || edge || IE_VER) { + height(wysiwygBody, '100%'); + + if (!IE_VER) { + on(wysiwygBody, 'touchend', base.focus); + } + } + + var tabIndex = attr(original, 'tabindex'); + attr(sourceEditor, 'tabindex', tabIndex); + attr(wysiwygEditor, 'tabindex', tabIndex); + + rangeHelper = new RangeHelper(wysiwygWindow); + + // load any textarea value into the editor + hide(original); + base.val(original.value); + + var placeholder = options.placeholder || + attr(original, 'placeholder'); + + if (placeholder) { + sourceEditor.placeholder = placeholder; + attr(wysiwygBody, 'placeholder', placeholder); + } + }; + + /** + * Initialises options + * @private + */ + initOptions = function () { + // auto-update original textbox on blur if option set to true + if (options.autoUpdate) { + on(wysiwygBody, 'blur', autoUpdate); + on(sourceEditor, 'blur', autoUpdate); + } + + if (options.rtl === null) { + options.rtl = css(sourceEditor, 'direction') === 'rtl'; + } + + base.rtl(!!options.rtl); + + if (options.autoExpand) { + // Need to update when images (or anything else) loads + on(wysiwygBody, 'load', autoExpand, EVENT_CAPTURE); + on(wysiwygBody, 'input keyup', autoExpand); + } + + if (options.resizeEnabled) { + initResize(); + } + + attr(editorContainer, 'id', options.id); + base.emoticons(options.emoticonsEnabled); + }; + + /** + * Initialises events + * @private + */ + initEvents = function () { + var form = original.form; + var compositionEvents = 'compositionstart compositionend'; + var eventsToForward = 'keydown keyup keypress focus blur contextmenu'; + var checkSelectionEvents = 'onselectionchange' in wysiwygDocument ? + 'selectionchange' : + 'keyup focus blur contextmenu mouseup touchend click'; + + on(globalDoc, 'click', handleDocumentClick); + + if (form) { + on(form, 'reset', handleFormReset); + on(form, 'submit', base.updateOriginal, EVENT_CAPTURE); + } + + on(wysiwygBody, 'keypress', handleKeyPress); + on(wysiwygBody, 'keydown', handleKeyDown); + on(wysiwygBody, 'keydown', handleBackSpace); + on(wysiwygBody, 'keyup', appendNewLine); + on(wysiwygBody, 'blur', valueChangedBlur); + on(wysiwygBody, 'keyup', valueChangedKeyUp); + on(wysiwygBody, 'paste', handlePasteEvt); + on(wysiwygBody, compositionEvents, handleComposition); + on(wysiwygBody, checkSelectionEvents, checkSelectionChanged); + on(wysiwygBody, eventsToForward, handleEvent); + + if (options.emoticonsCompat && globalWin.getSelection) { + on(wysiwygBody, 'keyup', emoticonsCheckWhitespace); + } + + on(wysiwygBody, 'blur', function () { + if (!base.val()) { + addClass(wysiwygBody, 'placeholder'); + } + }); + + on(wysiwygBody, 'focus', function () { + removeClass(wysiwygBody, 'placeholder'); + }); + + on(sourceEditor, 'blur', valueChangedBlur); + on(sourceEditor, 'keyup', valueChangedKeyUp); + on(sourceEditor, 'keydown', handleKeyDown); + on(sourceEditor, compositionEvents, handleComposition); + on(sourceEditor, eventsToForward, handleEvent); + + on(wysiwygDocument, 'mousedown', handleMouseDown); + on(wysiwygDocument, checkSelectionEvents, checkSelectionChanged); + on(wysiwygDocument, 'beforedeactivate keyup mouseup', saveRange); + on(wysiwygDocument, 'keyup', appendNewLine); + on(wysiwygDocument, 'focus', function () { + lastRange = null; + }); + + on(editorContainer, 'selectionchanged', checkNodeChanged); + on(editorContainer, 'selectionchanged', updateActiveButtons); + // Custom events to forward + on( + editorContainer, + 'selectionchanged valuechanged nodechanged pasteraw paste', + handleEvent + ); + }; + + /** + * Creates the toolbar and appends it to the container + * @private + */ + initToolBar = function () { + var group, + commands = base.commands, + exclude = (options.toolbarExclude || '').split(','), + groups = options.toolbar.split('|'); + + toolbar = createElement('div', { + className: 'sceditor-toolbar', + unselectable: 'on' + }); + + if (options.icons in SCEditor.icons) { + icons = new SCEditor.icons[options.icons](); + } + + each(groups, function (_, menuItems) { + group = createElement('div', { + className: 'sceditor-group' + }); + + each(menuItems.split(','), function (_, commandName) { + var button, shortcut, + command = commands[commandName]; + + // The commandName must be a valid command and not excluded + if (!command || exclude.indexOf(commandName) > -1) { + return; + } + + shortcut = command.shortcut; + button = _tmpl('toolbarButton', { + name: commandName, + dispName: base._(command.name || + command.tooltip || commandName) + }, true).firstChild; + + if (icons && icons.create) { + var icon = icons.create(commandName); + if (icon) { + insertBefore(icons.create(commandName), + button.firstChild); + addClass(button, 'has-icon'); + } + } + + button._sceTxtMode = !!command.txtExec; + button._sceWysiwygMode = !!command.exec; + toggleClass(button, 'disabled', !command.exec); + on(button, 'click', function (e) { + if (!hasClass(button, 'disabled')) { + handleCommand(button, command); + } + + updateActiveButtons(); + e.preventDefault(); + }); + // Prevent editor losing focus when button clicked + on(button, 'mousedown', function (e) { + base.closeDropDown(); + e.preventDefault(); + }); + + if (command.tooltip) { + attr(button, 'title', + base._(command.tooltip) + + (shortcut ? ' (' + shortcut + ')' : '') + ); + } + + if (shortcut) { + base.addShortcut(shortcut, commandName); + } + + if (command.state) { + btnStateHandlers.push({ + name: commandName, + state: command.state + }); + // exec string commands can be passed to queryCommandState + } else if (isString(command.exec)) { + btnStateHandlers.push({ + name: commandName, + state: command.exec + }); + } + + appendChild(group, button); + toolbarButtons[commandName] = button; + }); + + // Exclude empty groups + if (group.firstChild) { + appendChild(toolbar, group); + } + }); + + // Append the toolbar to the toolbarContainer option if given + appendChild(options.toolbarContainer || editorContainer, toolbar); + }; + + /** + * Creates the resizer. + * @private + */ + initResize = function () { + var minHeight, maxHeight, minWidth, maxWidth, + mouseMoveFunc, mouseUpFunc, + grip = createElement('div', { + className: 'sceditor-grip' + }), + // Cover is used to cover the editor iframe so document + // still gets mouse move events + cover = createElement('div', { + className: 'sceditor-resize-cover' + }), + moveEvents = 'touchmove mousemove', + endEvents = 'touchcancel touchend mouseup', + startX = 0, + startY = 0, + newX = 0, + newY = 0, + startWidth = 0, + startHeight = 0, + origWidth = width(editorContainer), + origHeight = height(editorContainer), + isDragging = false, + rtl = base.rtl(); + + minHeight = options.resizeMinHeight || origHeight / 1.5; + maxHeight = options.resizeMaxHeight || origHeight * 2.5; + minWidth = options.resizeMinWidth || origWidth / 1.25; + maxWidth = options.resizeMaxWidth || origWidth * 1.25; + + mouseMoveFunc = function (e) { + // iOS uses window.event + if (e.type === 'touchmove') { + e = globalWin.event; + newX = e.changedTouches[0].pageX; + newY = e.changedTouches[0].pageY; + } else { + newX = e.pageX; + newY = e.pageY; + } + + var newHeight = startHeight + (newY - startY), + newWidth = rtl ? + startWidth - (newX - startX) : + startWidth + (newX - startX); + + if (maxWidth > 0 && newWidth > maxWidth) { + newWidth = maxWidth; + } + if (minWidth > 0 && newWidth < minWidth) { + newWidth = minWidth; + } + if (!options.resizeWidth) { + newWidth = false; + } + + if (maxHeight > 0 && newHeight > maxHeight) { + newHeight = maxHeight; + } + if (minHeight > 0 && newHeight < minHeight) { + newHeight = minHeight; + } + if (!options.resizeHeight) { + newHeight = false; + } + + if (newWidth || newHeight) { + base.dimensions(newWidth, newHeight); + } + + e.preventDefault(); + }; + + mouseUpFunc = function (e) { + if (!isDragging) { + return; + } + + isDragging = false; + + hide(cover); + removeClass(editorContainer, 'resizing'); + off(globalDoc, moveEvents, mouseMoveFunc); + off(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }; + + if (icons && icons.create) { + var icon = icons.create('grip'); + if (icon) { + appendChild(grip, icon); + addClass(grip, 'has-icon'); + } + } + + appendChild(editorContainer, grip); + appendChild(editorContainer, cover); + hide(cover); + + on(grip, 'touchstart mousedown', function (e) { + // iOS uses window.event + if (e.type === 'touchstart') { + e = globalWin.event; + startX = e.touches[0].pageX; + startY = e.touches[0].pageY; + } else { + startX = e.pageX; + startY = e.pageY; + } + + startWidth = width(editorContainer); + startHeight = height(editorContainer); + isDragging = true; + + addClass(editorContainer, 'resizing'); + show(cover); + on(globalDoc, moveEvents, mouseMoveFunc); + on(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }); + }; + + /** + * Prefixes and preloads the emoticon images + * @private + */ + initEmoticons = function () { + var emoticons = options.emoticons; + var root = options.emoticonsRoot || ''; + + if (emoticons) { + allEmoticons = extend( + {}, emoticons.more, emoticons.dropdown, emoticons.hidden + ); + } + + each(allEmoticons, function (key, url) { + allEmoticons[key] = _tmpl('emoticon', { + key: key, + // Prefix emoticon root to emoticon urls + url: root + (url.url || url), + tooltip: url.tooltip || key + }); + + // Preload the emoticon + if (options.emoticonsEnabled) { + preLoadCache.push(createElement('img', { + src: root + (url.url || url) + })); + } + }); + }; + + /** + * Autofocus the editor + * @private + */ + autofocus = function () { + var range, txtPos, + node = wysiwygBody.firstChild, + focusEnd = !!options.autofocusEnd; + + // Can't focus invisible elements + if (!isVisible(editorContainer)) { + return; + } + + if (base.sourceMode()) { + txtPos = focusEnd ? sourceEditor.value.length : 0; + + sourceEditor.setSelectionRange(txtPos, txtPos); + + return; + } + + removeWhiteSpace(wysiwygBody); + + if (focusEnd) { + if (!(node = wysiwygBody.lastChild)) { + node = createElement('p', {}, wysiwygDocument); + appendChild(wysiwygBody, node); + } + + while (node.lastChild) { + node = node.lastChild; + + // IE < 11 should place the cursor after the
    as + // it will show it as a newline. IE >= 11 and all + // other browsers should place the cursor before. + if (!IE_BR_FIX$2 && is(node, 'br') && node.previousSibling) { + node = node.previousSibling; + } + } + } + + range = wysiwygDocument.createRange(); + + if (!canHaveChildren(node)) { + range.setStartBefore(node); + + if (focusEnd) { + range.setStartAfter(node); + } + } else { + range.selectNodeContents(node); + } + + range.collapse(!focusEnd); + rangeHelper.selectRange(range); + currentSelection = range; + + if (focusEnd) { + wysiwygBody.scrollTop = wysiwygBody.scrollHeight; + } + + base.focus(); + }; + + /** + * Gets if the editor is read only + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly + * @return {boolean} + */ + /** + * Sets if the editor is read only + * + * @param {boolean} readOnly + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly^2 + * @return {this} + */ + base.readOnly = function (readOnly) { + if (typeof readOnly !== 'boolean') { + return !sourceEditor.readonly; + } + + wysiwygBody.contentEditable = !readOnly; + sourceEditor.readonly = !readOnly; + + updateToolBar(readOnly); + + return base; + }; + + /** + * Gets if the editor is in RTL mode + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl + * @return {boolean} + */ + /** + * Sets if the editor is in RTL mode + * + * @param {boolean} rtl + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl^2 + * @return {this} + */ + base.rtl = function (rtl) { + var dir = rtl ? 'rtl' : 'ltr'; + + if (typeof rtl !== 'boolean') { + return attr(sourceEditor, 'dir') === 'rtl'; + } + + attr(wysiwygBody, 'dir', dir); + attr(sourceEditor, 'dir', dir); + + removeClass(editorContainer, 'rtl'); + removeClass(editorContainer, 'ltr'); + addClass(editorContainer, dir); + + if (icons && icons.rtl) { + icons.rtl(rtl); + } + + return base; + }; + + /** + * Updates the toolbar to disable/enable the appropriate buttons + * @private + */ + updateToolBar = function (disable) { + var mode = base.inSourceMode() ? '_sceTxtMode' : '_sceWysiwygMode'; + + each(toolbarButtons, function (_, button) { + toggleClass(button, 'disabled', disable || !button[mode]); + }); + }; + + /** + * Gets the width of the editor in pixels + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width + * @return {number} + */ + /** + * Sets the width of the editor + * + * @param {number} width Width in pixels + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width^2 + * @return {this} + */ + /** + * Sets the width of the editor + * + * The saveWidth specifies if to save the width. The stored width can be + * used for things like restoring from maximized state. + * + * @param {number} width Width in pixels + * @param {boolean} [saveWidth=true] If to store the width + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name width^3 + * @return {this} + */ + base.width = function (width$$1, saveWidth) { + if (!width$$1 && width$$1 !== 0) { + return width(editorContainer); + } + + base.dimensions(width$$1, null, saveWidth); + + return base; + }; + + /** + * Returns an object with the properties width and height + * which are the width and height of the editor in px. + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions + * @return {object} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^2 + * @return {this} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + *

    The save argument specifies if to save the new sizes. + * The saved sizes can be used for things like restoring from + * maximized state. This should normally be left as true.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @param {boolean} [save=true] If to store the new sizes + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^3 + * @return {this} + */ + base.dimensions = function (width$$1, height$$1, save) { + // set undefined width/height to boolean false + width$$1 = (!width$$1 && width$$1 !== 0) ? false : width$$1; + height$$1 = (!height$$1 && height$$1 !== 0) ? false : height$$1; + + if (width$$1 === false && height$$1 === false) { + return { width: base.width(), height: base.height() }; + } + + if (width$$1 !== false) { + if (save !== false) { + options.width = width$$1; + } + + width(editorContainer, width$$1); + } + + if (height$$1 !== false) { + if (save !== false) { + options.height = height$$1; + } + + height(editorContainer, height$$1); + } + + return base; + }; + + /** + * Gets the height of the editor in px + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height + * @return {number} + */ + /** + * Sets the height of the editor + * + * @param {number} height Height in px + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height^2 + * @return {this} + */ + /** + * Sets the height of the editor + * + * The saveHeight specifies if to save the height. + * + * The stored height can be used for things like + * restoring from maximized state. + * + * @param {number} height Height in px + * @param {boolean} [saveHeight=true] If to store the height + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name height^3 + * @return {this} + */ + base.height = function (height$$1, saveHeight) { + if (!height$$1 && height$$1 !== 0) { + return height(editorContainer); + } + + base.dimensions(null, height$$1, saveHeight); + + return base; + }; + + /** + * Gets if the editor is maximised or not + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize + * @return {boolean} + */ + /** + * Sets if the editor is maximised or not + * + * @param {boolean} maximize If to maximise the editor + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize^2 + * @return {this} + */ + base.maximize = function (maximize) { + var maximizeSize = 'sceditor-maximize'; + + if (isUndefined(maximize)) { + return hasClass(editorContainer, maximizeSize); + } + + maximize = !!maximize; + + if (maximize) { + maximizeScrollPosition = globalWin.pageYOffset; + } + + toggleClass(globalDoc.documentElement, maximizeSize, maximize); + toggleClass(globalDoc.body, maximizeSize, maximize); + toggleClass(editorContainer, maximizeSize, maximize); + base.width(maximize ? '100%' : options.width, false); + base.height(maximize ? '100%' : options.height, false); + + if (!maximize) { + globalWin.scrollTo(0, maximizeScrollPosition); + } + + autoExpand(); + + return base; + }; + + autoExpand = function () { + if (options.autoExpand && !autoExpandThrottle) { + autoExpandThrottle = setTimeout(base.expandToContent, 200); + } + }; + + /** + * Expands or shrinks the editors height to the height of it's content + * + * Unless ignoreMaxHeight is set to true it will not expand + * higher than the maxHeight option. + * + * @since 1.3.5 + * @param {boolean} [ignoreMaxHeight=false] + * @function + * @name expandToContent + * @memberOf SCEditor.prototype + * @see #resizeToContent + */ + base.expandToContent = function (ignoreMaxHeight) { + if (base.maximize()) { + return; + } + + clearTimeout(autoExpandThrottle); + autoExpandThrottle = false; + + if (!autoExpandBounds) { + var height$$1 = options.resizeMinHeight || options.height || + height(original); + + autoExpandBounds = { + min: height$$1, + max: options.resizeMaxHeight || (height$$1 * 2) + }; + } + + var range = globalDoc.createRange(); + range.selectNodeContents(wysiwygBody); + + var rect = range.getBoundingClientRect(); + var current = wysiwygDocument.documentElement.clientHeight - 1; + var spaceNeeded = rect.bottom - rect.top; + var newHeight = base.height() + 1 + (spaceNeeded - current); + + if (!ignoreMaxHeight && autoExpandBounds.max !== -1) { + newHeight = Math.min(newHeight, autoExpandBounds.max); + } + + base.height(Math.ceil(Math.max(newHeight, autoExpandBounds.min))); + }; + + /** + * Destroys the editor, removing all elements and + * event handlers. + * + * Leaves only the original textarea. + * + * @function + * @name destroy + * @memberOf SCEditor.prototype + */ + base.destroy = function () { + // Don't destroy if the editor has already been destroyed + if (!pluginManager) { + return; + } + + pluginManager.destroy(); + + rangeHelper = null; + lastRange = null; + pluginManager = null; + + if (dropdown) { + remove(dropdown); + } + + off(globalDoc, 'click', handleDocumentClick); + + // TODO: make off support null nodes? + var form = original.form; + if (form) { + off(form, 'reset', handleFormReset); + off(form, 'submit', base.updateOriginal); + } + + remove(sourceEditor); + remove(toolbar); + remove(editorContainer); + + delete original._sceditor; + show(original); + + original.required = isRequired; + }; + + + /** + * Creates a menu item drop down + * + * @param {HTMLElement} menuItem The button to align the dropdown with + * @param {string} name Used for styling the dropdown, will be + * a class sceditor-name + * @param {HTMLElement} content The HTML content of the dropdown + * @param {boolean} ieFix If to add the unselectable attribute + * to all the contents elements. Stops + * IE from deselecting the text in the + * editor + * @function + * @name createDropDown + * @memberOf SCEditor.prototype + */ + base.createDropDown = function (menuItem, name, content, ieFix) { + // first click for create second click for close + var dropDownCss, + dropDownClass = 'sceditor-' + name; + + // Will re-focus the editor. This is needed for IE + // as it has special logic to save/restore the selection + base.closeDropDown(true); + + // Only close the dropdown if it was already open + if (dropdown && hasClass(dropdown, dropDownClass)) { + return; + } + + // IE needs unselectable attr to stop it from + // unselecting the text in the editor. + // SCEditor can cope if IE does unselect the + // text it's just not nice. + if (ieFix !== false) { + each(find(content, ':not(input):not(textarea)'), + function (_, node) { + if (node.nodeType === ELEMENT_NODE) { + attr(node, 'unselectable', 'on'); + } + }); + } + + dropDownCss = extend({ + top: menuItem.offsetTop, + left: menuItem.offsetLeft, + marginTop: menuItem.clientHeight + }, options.dropDownCss); + + dropdown = createElement('div', { + className: 'sceditor-dropdown ' + dropDownClass + }); + + css(dropdown, dropDownCss); + appendChild(dropdown, content); + appendChild(editorContainer, dropdown); + on(dropdown, 'click focusin', function (e) { + // stop clicks within the dropdown from being handled + e.stopPropagation(); + }); + + // If try to focus the first input immediately IE will + // place the cursor at the start of the editor instead + // of focusing on the input. + setTimeout(function () { + if (dropdown) { + var first = find(dropdown, 'input,textarea')[0]; + if (first) { + first.focus(); + } + } + }); + }; + + /** + * Handles any document click and closes the dropdown if open + * @private + */ + handleDocumentClick = function (e) { + // ignore right clicks + if (e.which !== 3 && dropdown && !e.defaultPrevented) { + autoUpdate(); + + base.closeDropDown(); + } + }; + + /** + * Handles the WYSIWYG editors paste event + * @private + */ + handlePasteEvt = function (e) { + var isIeOrEdge = IE_VER || edge; + var editable = wysiwygBody; + var clipboard = e.clipboardData; + var loadImage = function (file) { + var reader = new FileReader(); + reader.onload = function (e) { + handlePasteData({ + html: '' + }); + }; + reader.readAsDataURL(file); + }; + + // Modern browsers with clipboard API - everything other than _very_ + // old android web views and UC browser which doesn't support the + // paste event at all. + if (clipboard && !isIeOrEdge) { + var data$$1 = {}; + var types = clipboard.types; + var items = clipboard.items; + + e.preventDefault(); + + for (var i = 0; i < types.length; i++) { + // Normalise image pasting to paste as a data-uri + if (globalWin.FileReader && items && + IMAGE_MIME_REGEX.test(items[i].type)) { + return loadImage(clipboard.items[i].getAsFile()); + } + + data$$1[types[i]] = clipboard.getData(types[i]); + } + // Call plugins here with file? + data$$1.text = data$$1['text/plain']; + data$$1.html = data$$1['text/html']; + + handlePasteData(data$$1); + // If contentsFragment exists then we are already waiting for a + // previous paste so let the handler for that handle this one too + } else if (!pasteContentFragment) { + // Save the scroll position so can be restored + // when contents is restored + var scrollTop = editable.scrollTop; + + rangeHelper.saveRange(); + + pasteContentFragment = globalDoc.createDocumentFragment(); + while (editable.firstChild) { + appendChild(pasteContentFragment, editable.firstChild); + } + + setTimeout(function () { + var html = editable.innerHTML; + + editable.innerHTML = ''; + appendChild(editable, pasteContentFragment); + editable.scrollTop = scrollTop; + pasteContentFragment = false; + + rangeHelper.restoreRange(); + + handlePasteData({ html: html }); + }, 0); + } + }; + + /** + * Gets the pasted data, filters it and then inserts it. + * @param {Object} data + * @private + */ + handlePasteData = function (data$$1) { + var pasteArea = createElement('div', {}, wysiwygDocument); + + pluginManager.call('pasteRaw', data$$1); + trigger(editorContainer, 'pasteraw', data$$1); + + if (data$$1.html) { + pasteArea.innerHTML = data$$1.html; + + // fix any invalid nesting + fixNesting(pasteArea); + } else { + pasteArea.innerHTML = entities(data$$1.text || ''); + } + + var paste = { + val: pasteArea.innerHTML + }; + + if ('fragmentToSource' in format) { + paste.val = format + .fragmentToSource(paste.val, wysiwygDocument, currentNode); + } + + pluginManager.call('paste', paste); + trigger(editorContainer, 'paste', paste); + + if ('fragmentToHtml' in format) { + paste.val = format + .fragmentToHtml(paste.val, currentNode); + } + + pluginManager.call('pasteHtml', paste); + + base.wysiwygEditorInsertHtml(paste.val, null, true); + }; + + /** + * Closes any currently open drop down + * + * @param {boolean} [focus=false] If to focus the editor + * after closing the drop down + * @function + * @name closeDropDown + * @memberOf SCEditor.prototype + */ + base.closeDropDown = function (focus) { + if (dropdown) { + remove(dropdown); + dropdown = null; + } + + if (focus === true) { + base.focus(); + } + }; + + + /** + * Inserts HTML into WYSIWYG editor. + * + * If endHtml is specified, any selected text will be placed + * between html and endHtml. If there is no selected text html + * and endHtml will just be concatenate together. + * + * @param {string} html + * @param {string} [endHtml=null] + * @param {boolean} [overrideCodeBlocking=false] If to insert the html + * into code tags, by + * default code tags only + * support text. + * @function + * @name wysiwygEditorInsertHtml + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertHtml = function ( + html, endHtml, overrideCodeBlocking + ) { + var marker, scrollTop, scrollTo, + editorHeight = height(wysiwygEditor); + + base.focus(); + + // TODO: This code tag should be configurable and + // should maybe convert the HTML into text instead + // Don't apply to code elements + if (!overrideCodeBlocking && closest(currentBlockNode, 'code')) { + return; + } + + // Insert the HTML and save the range so the editor can be scrolled + // to the end of the selection. Also allows emoticons to be replaced + // without affecting the cursor position + rangeHelper.insertHTML(html, endHtml); + rangeHelper.saveRange(); + replaceEmoticons(); + + // Scroll the editor after the end of the selection + marker = find(wysiwygBody, '#sceditor-end-marker')[0]; + show(marker); + scrollTop = wysiwygBody.scrollTop; + scrollTo = (getOffset(marker).top + + (marker.offsetHeight * 1.5)) - editorHeight; + hide(marker); + + // Only scroll if marker isn't already visible + if (scrollTo > scrollTop || scrollTo + editorHeight < scrollTop) { + wysiwygBody.scrollTop = scrollTo; + } + + triggerValueChanged(false); + rangeHelper.restoreRange(); + + // Add a new line after the last block element + // so can always add text after it + appendNewLine(); + }; + + /** + * Like wysiwygEditorInsertHtml except it will convert any HTML + * into text before inserting it. + * + * @param {string} text + * @param {string} [endText=null] + * @function + * @name wysiwygEditorInsertText + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertText = function (text, endText) { + base.wysiwygEditorInsertHtml( + entities(text), entities(endText) + ); + }; + + /** + * Inserts text into the WYSIWYG or source editor depending on which + * mode the editor is in. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.3.5 + * @function + * @name insertText + * @memberOf SCEditor.prototype + */ + base.insertText = function (text, endText) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(text, endText); + } else { + base.wysiwygEditorInsertText(text, endText); + } + + return base; + }; + + /** + * Like wysiwygEditorInsertHtml but inserts text into the + * source mode editor instead. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * The cursor will be placed after the text param. If endText is + * specified the cursor will be placed before endText, so passing:
    + * + * '[b]', '[/b]' + * + * Would cause the cursor to be placed:
    + * + * [b]Selected text|[/b] + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.4.0 + * @function + * @name sourceEditorInsertText + * @memberOf SCEditor.prototype + */ + base.sourceEditorInsertText = function (text, endText) { + var scrollTop, currentValue, + startPos = sourceEditor.selectionStart, + endPos = sourceEditor.selectionEnd; + + scrollTop = sourceEditor.scrollTop; + sourceEditor.focus(); + currentValue = sourceEditor.value; + + if (endText) { + text += currentValue.substring(startPos, endPos) + endText; + } + + sourceEditor.value = currentValue.substring(0, startPos) + + text + + currentValue.substring(endPos, currentValue.length); + + sourceEditor.selectionStart = (startPos + text.length) - + (endText ? endText.length : 0); + sourceEditor.selectionEnd = sourceEditor.selectionStart; + + sourceEditor.scrollTop = scrollTop; + sourceEditor.focus(); + + triggerValueChanged(); + }; + + /** + * Gets the current instance of the rangeHelper class + * for the editor. + * + * @return {RangeHelper} + * @function + * @name getRangeHelper + * @memberOf SCEditor.prototype + */ + base.getRangeHelper = function () { + return rangeHelper; + }; + + /** + * Gets or sets the source editor caret position. + * + * @param {Object} [position] + * @return {this} + * @function + * @since 1.4.5 + * @name sourceEditorCaret + * @memberOf SCEditor.prototype + */ + base.sourceEditorCaret = function (position) { + sourceEditor.focus(); + + if (position) { + sourceEditor.selectionStart = position.start; + sourceEditor.selectionEnd = position.end; + + return this; + } + + return { + start: sourceEditor.selectionStart, + end: sourceEditor.selectionEnd + }; + }; + + /** + * Gets the value of the editor. + * + * If the editor is in WYSIWYG mode it will return the filtered + * HTML from it (converted to BBCode if using the BBCode plugin). + * It it's in Source Mode it will return the unfiltered contents + * of the source editor (if using the BBCode plugin this will be + * BBCode again). + * + * @since 1.3.5 + * @return {string} + * @function + * @name val + * @memberOf SCEditor.prototype + */ + /** + * Sets the value of the editor. + * + * If filter set true the val will be passed through the filter + * function. If using the BBCode plugin it will pass the val to + * the BBCode filter to convert any BBCode into HTML. + * + * @param {string} val + * @param {boolean} [filter=true] + * @return {this} + * @since 1.3.5 + * @function + * @name val^2 + * @memberOf SCEditor.prototype + */ + base.val = function (val, filter) { + if (!isString(val)) { + return base.inSourceMode() ? + base.getSourceEditorValue(false) : + base.getWysiwygEditorValue(filter); + } + + if (!base.inSourceMode()) { + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + base.setWysiwygEditorValue(val); + } else { + base.setSourceEditorValue(val); + } + + return base; + }; + + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @return {this} + * @since 1.3.5 + * @function + * @name insert + * @memberOf SCEditor.prototype + */ + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * If the allowMixed param is set to true, HTML any will not be + * escaped + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @param {boolean} [allowMixed=false] + * @return {this} + * @since 1.4.3 + * @function + * @name insert^2 + * @memberOf SCEditor.prototype + */ + // eslint-disable-next-line max-params + base.insert = function ( + start, end, filter, convertEmoticons, allowMixed + ) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(start, end); + return base; + } + + // Add the selection between start and end + if (end) { + var html = rangeHelper.selectedHtml(); + + if (filter !== false && 'fragmentToSource' in format) { + html = format + .fragmentToSource(html, wysiwygDocument, currentNode); + } + + start += html + end; + } + // TODO: This filter should allow empty tags as it's inserting. + if (filter !== false && 'fragmentToHtml' in format) { + start = format.fragmentToHtml(start, currentNode); + } + + // Convert any escaped HTML back into HTML if mixed is allowed + if (filter !== false && allowMixed === true) { + start = start.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + } + + base.wysiwygEditorInsertHtml(start); + + return base; + }; + + /** + * Gets the WYSIWYG editors HTML value. + * + * If using a plugin that filters the Ht Ml like the BBCode plugin + * it will return the result of the filtering (BBCode) unless the + * filter param is set to false. + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @name getWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.getWysiwygEditorValue = function (filter) { + var html; + // Create a tmp node to store contents so it can be modified + // without affecting anything else. + var tmp = createElement('div', {}, wysiwygDocument); + var childNodes = wysiwygBody.childNodes; + + for (var i = 0; i < childNodes.length; i++) { + appendChild(tmp, childNodes[i].cloneNode(true)); + } + + appendChild(wysiwygBody, tmp); + fixNesting(tmp); + remove(tmp); + + html = tmp.innerHTML; + + // filter the HTML and DOM through any plugins + if (filter !== false && format.hasOwnProperty('toSource')) { + html = format.toSource(html, wysiwygDocument); + } + + return html; + }; + + /** + * Gets the WYSIWYG editor's iFrame Body. + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getBody + * @memberOf SCEditor.prototype + */ + base.getBody = function () { + return wysiwygBody; + }; + + /** + * Gets the WYSIWYG editors container area (whole iFrame). + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getContentAreaContainer + * @memberOf SCEditor.prototype + */ + base.getContentAreaContainer = function () { + return wysiwygEditor; + }; + + /** + * Gets the text editor value + * + * If using a plugin that filters the text like the BBCode plugin + * it will return the result of the filtering which is BBCode to + * HTML so it will return HTML. If filter is set to false it will + * just return the contents of the source editor (BBCode). + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @since 1.4.0 + * @name getSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.getSourceEditorValue = function (filter) { + var val = sourceEditor.value; + + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + return val; + }; + + /** + * Sets the WYSIWYG HTML editor value. Should only be the HTML + * contained within the body tags + * + * @param {string} value + * @function + * @name setWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.setWysiwygEditorValue = function (value) { + if (!value) { + value = '

    ' + (IE_VER ? '' : '
    ') + '

    '; + } + + wysiwygBody.innerHTML = value; + replaceEmoticons(); + + appendNewLine(); + triggerValueChanged(); + autoExpand(); + }; + + /** + * Sets the text editor value + * + * @param {string} value + * @function + * @name setSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.setSourceEditorValue = function (value) { + sourceEditor.value = value; + + triggerValueChanged(); + }; + + /** + * Updates the textarea that the editor is replacing + * with the value currently inside the editor. + * + * @function + * @name updateOriginal + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.updateOriginal = function () { + original.value = base.val(); + }; + + /** + * Replaces any emoticon codes in the passed HTML + * with their emoticon images + * @private + */ + replaceEmoticons = function () { + if (options.emoticonsEnabled) { + replace(wysiwygBody, allEmoticons, options.emoticonsCompat); + } + }; + + /** + * If the editor is in source code mode + * + * @return {boolean} + * @function + * @name inSourceMode + * @memberOf SCEditor.prototype + */ + base.inSourceMode = function () { + return hasClass(editorContainer, 'sourceMode'); + }; + + /** + * Gets if the editor is in sourceMode + * + * @return boolean + * @function + * @name sourceMode + * @memberOf SCEditor.prototype + */ + /** + * Sets if the editor is in sourceMode + * + * @param {boolean} enable + * @return {this} + * @function + * @name sourceMode^2 + * @memberOf SCEditor.prototype + */ + base.sourceMode = function (enable) { + var inSourceMode = base.inSourceMode(); + + if (typeof enable !== 'boolean') { + return inSourceMode; + } + + if ((inSourceMode && !enable) || (!inSourceMode && enable)) { + base.toggleSourceMode(); + } + + return base; + }; + + /** + * Switches between the WYSIWYG and source modes + * + * @function + * @name toggleSourceMode + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.toggleSourceMode = function () { + var isInSourceMode = base.inSourceMode(); + + // don't allow switching to WYSIWYG if doesn't support it + if (!isWysiwygSupported && isInSourceMode) { + return; + } + + if (!isInSourceMode) { + rangeHelper.saveRange(); + rangeHelper.clear(); + } + + base.blur(); + + if (isInSourceMode) { + base.setWysiwygEditorValue(base.getSourceEditorValue()); + } else { + base.setSourceEditorValue(base.getWysiwygEditorValue()); + } + + lastRange = null; + toggle(sourceEditor); + toggle(wysiwygEditor); + + toggleClass(editorContainer, 'wysiwygMode', isInSourceMode); + toggleClass(editorContainer, 'sourceMode', !isInSourceMode); + + updateToolBar(); + updateActiveButtons(); + }; + + /** + * Gets the selected text of the source editor + * @return {string} + * @private + */ + sourceEditorSelectedText = function () { + sourceEditor.focus(); + + return sourceEditor.value.substring( + sourceEditor.selectionStart, + sourceEditor.selectionEnd + ); + }; + + /** + * Handles the passed command + * @private + */ + handleCommand = function (caller, cmd) { + // check if in text mode and handle text commands + if (base.inSourceMode()) { + if (cmd.txtExec) { + if (Array.isArray(cmd.txtExec)) { + base.sourceEditorInsertText.apply(base, cmd.txtExec); + } else { + cmd.txtExec.call(base, caller, sourceEditorSelectedText()); + } + } + } else if (cmd.exec) { + if (isFunction(cmd.exec)) { + cmd.exec.call(base, caller); + } else { + base.execCommand( + cmd.exec, + cmd.hasOwnProperty('execParam') ? cmd.execParam : null + ); + } + } + + }; + + /** + * Saves the current range. Needed for IE because it forgets + * where the cursor was and what was selected + * @private + */ + saveRange = function () { + /* this is only needed for IE */ + if (IE_VER) { + lastRange = rangeHelper.selectedRange(); + } + }; + + /** + * Executes a command on the WYSIWYG editor + * + * @param {string} command + * @param {String|Boolean} [param] + * @function + * @name execCommand + * @memberOf SCEditor.prototype + */ + base.execCommand = function (command, param) { + var executed = false, + commandObj = base.commands[command]; + + base.focus(); + + // TODO: make configurable + // don't apply any commands to code elements + if (closest(rangeHelper.parentNode(), 'code')) { + return; + } + + try { + executed = wysiwygDocument.execCommand(command, false, param); + } catch (ex) { } + + // show error if execution failed and an error message exists + if (!executed && commandObj && commandObj.errorMessage) { + /*global alert:false*/ + alert(base._(commandObj.errorMessage)); + } + + updateActiveButtons(); + }; + + /** + * Checks if the current selection has changed and triggers + * the selectionchanged event if it has. + * + * In browsers other than IE, it will check at most once every 100ms. + * This is because only IE has a selection changed event. + * @private + */ + checkSelectionChanged = function () { + function check() { + // Don't create new selection if there isn't one (like after + // blur event in iOS) + if (wysiwygWindow.getSelection() && + wysiwygWindow.getSelection().rangeCount <= 0) { + currentSelection = null; + // rangeHelper could be null if editor was destroyed + // before the timeout had finished + } else if (rangeHelper && !rangeHelper.compare(currentSelection)) { + currentSelection = rangeHelper.cloneSelected(); + + // If the selection is in an inline wrap it in a block. + // Fixes #331 + if (currentSelection && currentSelection.collapsed) { + var parent$$1 = currentSelection.startContainer; + var offset = currentSelection.startOffset; + + // Handle if selection is placed before/after an element + if (offset && parent$$1.nodeType !== TEXT_NODE) { + parent$$1 = parent$$1.childNodes[offset]; + } + + while (parent$$1 && parent$$1.parentNode !== wysiwygBody) { + parent$$1 = parent$$1.parentNode; + } + + if (parent$$1 && isInline(parent$$1, true)) { + rangeHelper.saveRange(); + wrapInlines(wysiwygBody, wysiwygDocument); + rangeHelper.restoreRange(); + } + } + + trigger(editorContainer, 'selectionchanged'); + } + + isSelectionCheckPending = false; + } + + if (isSelectionCheckPending) { + return; + } + + isSelectionCheckPending = true; + + // Don't need to limit checking if browser supports the Selection API + if ('onselectionchange' in wysiwygDocument) { + check(); + } else { + setTimeout(check, 100); + } + }; + + /** + * Checks if the current node has changed and triggers + * the nodechanged event if it has + * @private + */ + checkNodeChanged = function () { + // check if node has changed + var oldNode, + node = rangeHelper.parentNode(); + + if (currentNode !== node) { + oldNode = currentNode; + currentNode = node; + currentBlockNode = rangeHelper.getFirstBlockParent(node); + + trigger(editorContainer, 'nodechanged', { + oldNode: oldNode, + newNode: currentNode + }); + } + }; + + /** + * Gets the current node that contains the selection/caret in + * WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentNode + * @memberOf SCEditor.prototype + */ + base.currentNode = function () { + return currentNode; + }; + + /** + * Gets the first block level node that contains the + * selection/caret in WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentBlockNode + * @memberOf SCEditor.prototype + * @since 1.4.4 + */ + base.currentBlockNode = function () { + return currentBlockNode; + }; + + /** + * Updates if buttons are active or not + * @private + */ + updateActiveButtons = function () { + var firstBlock, parent$$1; + var activeClass = 'active'; + var doc = wysiwygDocument; + var isSource = base.sourceMode(); + + if (base.readOnly()) { + each(find(toolbar, activeClass), function (_, menuItem) { + removeClass(menuItem, activeClass); + }); + return; + } + + if (!isSource) { + parent$$1 = rangeHelper.parentNode(); + firstBlock = rangeHelper.getFirstBlockParent(parent$$1); + } + + for (var j = 0; j < btnStateHandlers.length; j++) { + var state = 0; + var btn = toolbarButtons[btnStateHandlers[j].name]; + var stateFn = btnStateHandlers[j].state; + var isDisabled = (isSource && !btn._sceTxtMode) || + (!isSource && !btn._sceWysiwygMode); + + if (isString(stateFn)) { + if (!isSource) { + try { + state = doc.queryCommandEnabled(stateFn) ? 0 : -1; + + // eslint-disable-next-line max-depth + if (state > -1) { + state = doc.queryCommandState(stateFn) ? 1 : 0; + } + } catch (ex) {} + } + } else if (!isDisabled) { + state = stateFn.call(base, parent$$1, firstBlock); + } + + toggleClass(btn, 'disabled', isDisabled || state < 0); + toggleClass(btn, activeClass, state > 0); + } + + if (icons && icons.update) { + icons.update(isSource, parent$$1, firstBlock); + } + }; + + /** + * Handles any key press in the WYSIWYG editor + * + * @private + */ + handleKeyPress = function (e) { + // FF bug: https://bugzilla.mozilla.org/show_bug.cgi?id=501496 + if (e.defaultPrevented) { + return; + } + + base.closeDropDown(); + + // 13 = enter key + if (e.which === 13) { + var LIST_TAGS = 'li,ul,ol'; + + // "Fix" (cludge) for blocklevel elements being duplicated in some + // browsers when enter is pressed instead of inserting a newline + if (!is(currentBlockNode, LIST_TAGS) && + hasStyling(currentBlockNode)) { + lastRange = null; + + var br = createElement('br', {}, wysiwygDocument); + rangeHelper.insertNode(br); + + // Last
    of a block will be collapsed unless it is + // IE < 11 so need to make sure the
    that was inserted + // isn't the last node of a block. + if (!IE_BR_FIX$2) { + var parent$$1 = br.parentNode; + var lastChild = parent$$1.lastChild; + + // Sometimes an empty next node is created after the
    + if (lastChild && lastChild.nodeType === TEXT_NODE && + lastChild.nodeValue === '') { + remove(lastChild); + lastChild = parent$$1.lastChild; + } + + // If this is the last BR of a block and the previous + // sibling is inline then will need an extra BR. This + // is needed because the last BR of a block will be + // collapsed. Fixes issue #248 + if (!isInline(parent$$1, true) && lastChild === br && + isInline(br.previousSibling)) { + rangeHelper.insertHTML('
    '); + } + } + + e.preventDefault(); + } + } + }; + + /** + * Makes sure that if there is a code or quote tag at the + * end of the editor, that there is a new line after it. + * + * If there wasn't a new line at the end you wouldn't be able + * to enter any text after a code/quote tag + * @return {void} + * @private + */ + appendNewLine = function () { + // Check all nodes in reverse until either add a new line + // or reach a non-empty textnode or BR at which point can + // stop checking. + rTraverse(wysiwygBody, function (node) { + // Last block, add new line after if has styling + if (node.nodeType === ELEMENT_NODE && + !/inline/.test(css(node, 'display'))) { + + // Add line break after if has styling + if (!is(node, '.sceditor-nlf') && hasStyling(node)) { + var paragraph = createElement('p', {}, wysiwygDocument); + paragraph.className = 'sceditor-nlf'; + paragraph.innerHTML = !IE_BR_FIX$2 ? '
    ' : ''; + appendChild(wysiwygBody, paragraph); + return false; + } + } + + // Last non-empty text node or line break. + // No need to add line-break after them + if ((node.nodeType === 3 && !/^\s*$/.test(node.nodeValue)) || + is(node, 'br')) { + return false; + } + }); + }; + + /** + * Handles form reset event + * @private + */ + handleFormReset = function () { + base.val(original.value); + }; + + /** + * Handles any mousedown press in the WYSIWYG editor + * @private + */ + handleMouseDown = function () { + base.closeDropDown(); + lastRange = null; + }; + + /** + * Translates the string into the locale language. + * + * Replaces any {0}, {1}, {2}, ect. with the params provided. + * + * @param {string} str + * @param {...String} args + * @return {string} + * @function + * @name _ + * @memberOf SCEditor.prototype + */ + base._ = function () { + var undef, + args = arguments; + + if (locale && locale[args[0]]) { + args[0] = locale[args[0]]; + } + + return args[0].replace(/\{(\d+)\}/g, function (str, p1) { + return args[p1 - 0 + 1] !== undef ? + args[p1 - 0 + 1] : + '{' + p1 + '}'; + }); + }; + + /** + * Passes events on to any handlers + * @private + * @return void + */ + handleEvent = function (e) { + if (pluginManager) { + // Send event to all plugins + pluginManager.call(e.type + 'Event', e, base); + } + + // convert the event into a custom event to send + var name = (e.target === sourceEditor ? 'scesrc' : 'scewys') + e.type; + + if (eventHandlers[name]) { + eventHandlers[name].forEach(function (fn) { + fn.call(base, e); + }); + } + }; + + /** + * Binds a handler to the specified events + * + * This function only binds to a limited list of + * supported events. + * + * The supported events are: + * + * * keyup + * * keydown + * * Keypress + * * blur + * * focus + * * nodechanged - When the current node containing + * the selection changes in WYSIWYG mode + * * contextmenu + * * selectionchanged + * * valuechanged + * + * + * The events param should be a string containing the event(s) + * to bind this handler to. If multiple, they should be separated + * by spaces. + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name bind + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.bind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + var wysEvent = 'scewys' + events[i]; + var srcEvent = 'scesrc' + events[i]; + // Use custom events to allow passing the instance as the + // 2nd argument. + // Also allows unbinding without unbinding the editors own + // event handlers. + if (!excludeWysiwyg) { + eventHandlers[wysEvent] = eventHandlers[wysEvent] || []; + eventHandlers[wysEvent].push(handler); + } + + if (!excludeSource) { + eventHandlers[srcEvent] = eventHandlers[srcEvent] || []; + eventHandlers[srcEvent].push(handler); + } + + // Start sending value changed events + if (events[i] === 'valuechanged') { + triggerValueChanged.hasHandler = true; + } + } + } + + return base; + }; + + /** + * Unbinds an event that was bound using bind(). + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude unbinding this + * handler from the WYSIWYG editor + * @param {boolean} excludeSource if to exclude unbinding this + * handler from the source editor + * @return {this} + * @function + * @name unbind + * @memberOf SCEditor.prototype + * @since 1.4.1 + * @see bind + */ + base.unbind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + if (!excludeWysiwyg) { + arrayRemove( + eventHandlers['scewys' + events[i]] || [], handler); + } + + if (!excludeSource) { + arrayRemove( + eventHandlers['scesrc' + events[i]] || [], handler); + } + } + } + + return base; + }; + + /** + * Blurs the editors input area + * + * @return {this} + * @function + * @name blur + * @memberOf SCEditor.prototype + * @since 1.3.6 + */ + /** + * Adds a handler to the editors blur event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name blur^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.blur = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('blur', handler, excludeWysiwyg, excludeSource); + } else if (!base.sourceMode()) { + wysiwygBody.blur(); + } else { + sourceEditor.blur(); + } + + return base; + }; + + /** + * Focuses the editors input area + * + * @return {this} + * @function + * @name focus + * @memberOf SCEditor.prototype + */ + /** + * Adds an event handler to the focus event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name focus^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.focus = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('focus', handler, excludeWysiwyg, excludeSource); + } else if (!base.inSourceMode()) { + // Already has focus so do nothing + if (find(wysiwygDocument, ':focus').length) { + return; + } + + var container; + var rng = rangeHelper.selectedRange(); + + // Fix FF bug where it shows the cursor in the wrong place + // if the editor hasn't had focus before. See issue #393 + if (!currentSelection) { + autofocus(); + } + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$2 && rng && rng.endOffset === 1 && rng.collapsed) { + container = rng.endContainer; + + if (container && container.childNodes.length === 1 && + is(container.firstChild, 'br')) { + rng.setStartBefore(container.firstChild); + rng.collapse(true); + rangeHelper.selectRange(rng); + } + } + + wysiwygWindow.focus(); + wysiwygBody.focus(); + + // Needed for IE + if (lastRange) { + rangeHelper.selectRange(lastRange); + + // Remove the stored range after being set. + // If the editor loses focus it should be saved again. + lastRange = null; + } + } else { + sourceEditor.focus(); + } + + updateActiveButtons(); + + return base; + }; + + /** + * Adds a handler to the key down event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyDown + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyDown = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keydown', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key press event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyPress + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyPress = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('keypress', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key up event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyUp + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyUp = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keyup', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the node changed event. + * + * Happens whenever the node containing the selection/caret + * changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name nodeChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.nodeChanged = function (handler) { + return base.bind('nodechanged', handler, false, true); + }; + + /** + * Adds a handler to the selection changed event + * + * Happens whenever the selection changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name selectionChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.selectionChanged = function (handler) { + return base.bind('selectionchanged', handler, false, true); + }; + + /** + * Adds a handler to the value changed event + * + * Happens whenever the current editor value changes. + * + * Whenever anything is inserted, the value changed or + * 1.5 secs after text is typed. If a space is typed it will + * cause the event to be triggered immediately instead of + * after 1.5 seconds + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name valueChanged + * @memberOf SCEditor.prototype + * @since 1.4.5 + */ + base.valueChanged = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('valuechanged', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Emoticons keypress handler + * @private + */ + emoticonsKeyPress = function (e) { + var replacedEmoticon, + cachePos = 0, + emoticonsCache = base.emoticonsCache, + curChar = String.fromCharCode(e.which); + + // TODO: Make configurable + if (closest(currentBlockNode, 'code')) { + return; + } + + if (!emoticonsCache) { + emoticonsCache = []; + + each(allEmoticons, function (key, html) { + emoticonsCache[cachePos++] = [key, html]; + }); + + emoticonsCache.sort(function (a, b) { + return a[0].length - b[0].length; + }); + + base.emoticonsCache = emoticonsCache; + base.longestEmoticonCode = + emoticonsCache[emoticonsCache.length - 1][0].length; + } + + replacedEmoticon = rangeHelper.replaceKeyword( + base.emoticonsCache, + true, + true, + base.longestEmoticonCode, + options.emoticonsCompat, + curChar + ); + + if (replacedEmoticon) { + if (!options.emoticonsCompat || !/^\s$/.test(curChar)) { + e.preventDefault(); + } + } + }; + + /** + * Makes sure emoticons are surrounded by whitespace + * @private + */ + emoticonsCheckWhitespace = function () { + checkWhitespace(currentBlockNode, rangeHelper); + }; + + /** + * Gets if emoticons are currently enabled + * @return {boolean} + * @function + * @name emoticons + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + /** + * Enables/disables emoticons + * + * @param {boolean} enable + * @return {this} + * @function + * @name emoticons^2 + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + base.emoticons = function (enable) { + if (!enable && enable !== false) { + return options.emoticonsEnabled; + } + + options.emoticonsEnabled = enable; + + if (enable) { + on(wysiwygBody, 'keypress', emoticonsKeyPress); + + if (!base.sourceMode()) { + rangeHelper.saveRange(); + + replaceEmoticons(); + triggerValueChanged(false); + + rangeHelper.restoreRange(); + } + } else { + var emoticons = + find(wysiwygBody, 'img[data-sceditor-emoticon]'); + + each(emoticons, function (_, img) { + var text = data(img, 'sceditor-emoticon'); + var textNode = wysiwygDocument.createTextNode(text); + img.parentNode.replaceChild(textNode, img); + }); + + off(wysiwygBody, 'keypress', emoticonsKeyPress); + + triggerValueChanged(); + } + + return base; + }; + + /** + * Gets the current WYSIWYG editors inline CSS + * + * @return {string} + * @function + * @name css + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + /** + * Sets inline CSS for the WYSIWYG editor + * + * @param {string} css + * @return {this} + * @function + * @name css^2 + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + base.css = function (css$$1) { + if (!inlineCss) { + inlineCss = createElement('style', { + id: 'inline' + }, wysiwygDocument); + + appendChild(wysiwygDocument.head, inlineCss); + } + + if (!isString(css$$1)) { + return inlineCss.styleSheet ? + inlineCss.styleSheet.cssText : inlineCss.innerHTML; + } + + if (inlineCss.styleSheet) { + inlineCss.styleSheet.cssText = css$$1; + } else { + inlineCss.innerHTML = css$$1; + } + + return base; + }; + + /** + * Handles the keydown event, used for shortcuts + * @private + */ + handleKeyDown = function (e) { + var shortcut = [], + SHIFT_KEYS = { + '`': '~', + '1': '!', + '2': '@', + '3': '#', + '4': '$', + '5': '%', + '6': '^', + '7': '&', + '8': '*', + '9': '(', + '0': ')', + '-': '_', + '=': '+', + ';': ': ', + '\'': '"', + ',': '<', + '.': '>', + '/': '?', + '\\': '|', + '[': '{', + ']': '}' + }, + SPECIAL_KEYS = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 19: 'pause', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'insert', + 46: 'del', + 91: 'win', + 92: 'win', + 93: 'select', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9', + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111: '/', + 112: 'f1', + 113: 'f2', + 114: 'f3', + 115: 'f4', + 116: 'f5', + 117: 'f6', + 118: 'f7', + 119: 'f8', + 120: 'f9', + 121: 'f10', + 122: 'f11', + 123: 'f12', + 144: 'numlock', + 145: 'scrolllock', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }, + NUMPAD_SHIFT_KEYS = { + 109: '-', + 110: 'del', + 111: '/', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }, + which = e.which, + character = SPECIAL_KEYS[which] || + String.fromCharCode(which).toLowerCase(); + + if (e.ctrlKey || e.metaKey) { + shortcut.push('ctrl'); + } + + if (e.altKey) { + shortcut.push('alt'); + } + + if (e.shiftKey) { + shortcut.push('shift'); + + if (NUMPAD_SHIFT_KEYS[which]) { + character = NUMPAD_SHIFT_KEYS[which]; + } else if (SHIFT_KEYS[character]) { + character = SHIFT_KEYS[character]; + } + } + + // Shift is 16, ctrl is 17 and alt is 18 + if (character && (which < 16 || which > 18)) { + shortcut.push(character); + } + + shortcut = shortcut.join('+'); + if (shortcutHandlers[shortcut] && + shortcutHandlers[shortcut].call(base) === false) { + + e.stopPropagation(); + e.preventDefault(); + } + }; + + /** + * Adds a shortcut handler to the editor + * @param {string} shortcut + * @param {String|Function} cmd + * @return {sceditor} + */ + base.addShortcut = function (shortcut, cmd) { + shortcut = shortcut.toLowerCase(); + + if (isString(cmd)) { + shortcutHandlers[shortcut] = function () { + handleCommand(toolbarButtons[cmd], base.commands[cmd]); + + return false; + }; + } else { + shortcutHandlers[shortcut] = cmd; + } + + return base; + }; + + /** + * Removes a shortcut handler + * @param {string} shortcut + * @return {sceditor} + */ + base.removeShortcut = function (shortcut) { + delete shortcutHandlers[shortcut.toLowerCase()]; + + return base; + }; + + /** + * Handles the backspace key press + * + * Will remove block styling like quotes/code ect if at the start. + * @private + */ + handleBackSpace = function (e) { + var node, offset, range, parent$$1; + + // 8 is the backspace key + if (options.disableBlockRemove || e.which !== 8 || + !(range = rangeHelper.selectedRange())) { + return; + } + + node = range.startContainer; + offset = range.startOffset; + + if (offset !== 0 || !(parent$$1 = currentStyledBlockNode()) || + is(parent$$1, 'body')) { + return; + } + + while (node !== parent$$1) { + while (node.previousSibling) { + node = node.previousSibling; + + // Everything but empty text nodes before the cursor + // should prevent the style from being removed + if (node.nodeType !== TEXT_NODE || node.nodeValue) { + return; + } + } + + if (!(node = node.parentNode)) { + return; + } + } + + // The backspace was pressed at the start of + // the container so clear the style + base.clearBlockFormatting(parent$$1); + e.preventDefault(); + }; + + /** + * Gets the first styled block node that contains the cursor + * @return {HTMLElement} + */ + currentStyledBlockNode = function () { + var block = currentBlockNode; + + while (!hasStyling(block) || isInline(block, true)) { + if (!(block = block.parentNode) || is(block, 'body')) { + return; + } + } + + return block; + }; + + /** + * Clears the formatting of the passed block element. + * + * If block is false, if will clear the styling of the first + * block level element that contains the cursor. + * @param {HTMLElement} block + * @since 1.4.4 + */ + base.clearBlockFormatting = function (block) { + block = block || currentStyledBlockNode(); + + if (!block || is(block, 'body')) { + return base; + } + + rangeHelper.saveRange(); + + block.className = ''; + lastRange = null; + + attr(block, 'style', ''); + + if (!is(block, 'p,div,td')) { + convertElement(block, 'p'); + } + + rangeHelper.restoreRange(); + return base; + }; + + /** + * Triggers the valueChanged signal if there is + * a plugin that handles it. + * + * If rangeHelper.saveRange() has already been + * called, then saveRange should be set to false + * to prevent the range being saved twice. + * + * @since 1.4.5 + * @param {boolean} saveRange If to call rangeHelper.saveRange(). + * @private + */ + triggerValueChanged = function (saveRange) { + if (!pluginManager || + (!pluginManager.hasHandler('valuechangedEvent') && + !triggerValueChanged.hasHandler)) { + return; + } + + var currentHtml, + sourceMode = base.sourceMode(), + hasSelection = !sourceMode && rangeHelper.hasSelection(); + + // Composition end isn't guaranteed to fire but must have + // ended when triggerValueChanged() is called so reset it + isComposing = false; + + // Don't need to save the range if sceditor-start-marker + // is present as the range is already saved + saveRange = saveRange !== false && + !wysiwygDocument.getElementById('sceditor-start-marker'); + + // Clear any current timeout as it's now been triggered + if (valueChangedKeyUpTimer) { + clearTimeout(valueChangedKeyUpTimer); + valueChangedKeyUpTimer = false; + } + + if (hasSelection && saveRange) { + rangeHelper.saveRange(); + } + + currentHtml = sourceMode ? sourceEditor.value : wysiwygBody.innerHTML; + + // Only trigger if something has actually changed. + if (currentHtml !== triggerValueChanged.lastVal) { + triggerValueChanged.lastVal = currentHtml; + + trigger(editorContainer, 'valuechanged', { + rawValue: sourceMode ? base.val() : currentHtml + }); + } + + if (hasSelection && saveRange) { + rangeHelper.removeMarkers(); + } + }; + + /** + * Should be called whenever there is a blur event + * @private + */ + valueChangedBlur = function () { + if (valueChangedKeyUpTimer) { + triggerValueChanged(); + } + }; + + /** + * Should be called whenever there is a keypress event + * @param {Event} e The keypress event + * @private + */ + valueChangedKeyUp = function (e) { + var which = e.which, + lastChar = valueChangedKeyUp.lastChar, + lastWasSpace = (lastChar === 13 || lastChar === 32), + lastWasDelete = (lastChar === 8 || lastChar === 46); + + valueChangedKeyUp.lastChar = which; + + if (isComposing) { + return; + } + + // 13 = return & 32 = space + if (which === 13 || which === 32) { + if (!lastWasSpace) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + // 8 = backspace & 46 = del + } else if (which === 8 || which === 46) { + if (!lastWasDelete) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + } else if (valueChangedKeyUp.triggerNext) { + triggerValueChanged(); + valueChangedKeyUp.triggerNext = false; + } + + // Clear the previous timeout and set a new one. + clearTimeout(valueChangedKeyUpTimer); + + // Trigger the event 1.5s after the last keypress if space + // isn't pressed. This might need to be lowered, will need + // to look into what the slowest average Chars Per Min is. + valueChangedKeyUpTimer = setTimeout(function () { + if (!isComposing) { + triggerValueChanged(); + } + }, 1500); + }; + + handleComposition = function (e) { + isComposing = /start/i.test(e.type); + + if (!isComposing) { + triggerValueChanged(); + } + }; + + autoUpdate = function () { + base.updateOriginal(); + }; + + // run the initializer + init(); + } + + + /** + * Map containing the loaded SCEditor locales + * @type {Object} + * @name locale + * @memberOf sceditor + */ + SCEditor.locale = {}; + + SCEditor.formats = {}; + SCEditor.icons = {}; + + + /** + * Static command helper class + * @class command + * @name sceditor.command + */ + SCEditor.command = + /** @lends sceditor.command */ + { + /** + * Gets a command + * + * @param {string} name + * @return {Object|null} + * @since v1.3.5 + */ + get: function (name) { + return defaultCmds[name] || null; + }, + + /** + *

    Adds a command to the editor or updates an existing + * command if a command with the specified name already exists.

    + * + *

    Once a command is add it can be included in the toolbar by + * adding it's name to the toolbar option in the constructor. It + * can also be executed manually by calling + * {@link sceditor.execCommand}

    + * + * @example + * SCEditor.command.set("hello", + * { + * exec: function () { + * alert("Hello World!"); + * } + * }); + * + * @param {string} name + * @param {Object} cmd + * @return {this|false} Returns false if name or cmd is false + * @since v1.3.5 + */ + set: function (name, cmd) { + if (!name || !cmd) { + return false; + } + + // merge any existing command properties + cmd = extend(defaultCmds[name] || {}, cmd); + + cmd.remove = function () { + SCEditor.command.remove(name); + }; + + defaultCmds[name] = cmd; + return this; + }, + + /** + * Removes a command + * + * @param {string} name + * @return {this} + * @since v1.3.5 + */ + remove: function (name) { + if (defaultCmds[name]) { + delete defaultCmds[name]; + } + + return this; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + */ + + window.sceditor = { + command: SCEditor.command, + commands: defaultCmds, + defaultOptions: defaultOptions, + + ie: ie, + ios: ios, + isWysiwygSupported: isWysiwygSupported, + + regexEscape: regex, + escapeEntities: entities, + escapeUriScheme: uriScheme, + + dom: { + css: css, + attr: attr, + removeAttr: removeAttr, + is: is, + closest: closest, + width: width, + height: height, + traverse: traverse, + rTraverse: rTraverse, + parseHTML: parseHTML, + hasStyling: hasStyling, + convertElement: convertElement, + blockLevelList: blockLevelList, + canHaveChildren: canHaveChildren, + isInline: isInline, + copyCSS: copyCSS, + fixNesting: fixNesting, + findCommonAncestor: findCommonAncestor, + getSibling: getSibling, + removeWhiteSpace: removeWhiteSpace, + extractContents: extractContents, + getOffset: getOffset, + getStyle: getStyle, + hasStyle: hasStyle + }, + locale: SCEditor.locale, + icons: SCEditor.icons, + utils: { + each: each, + isEmptyObject: isEmptyObject, + extend: extend + }, + plugins: PluginManager.plugins, + formats: SCEditor.formats, + create: function (textarea, options) { + options = options || {}; + + // Don't allow the editor to be initialised + // on it's own source editor + if (parent(textarea, '.sceditor-container')) { + return; + } + + if (options.runWithoutWysiwygSupport || isWysiwygSupported) { + /*eslint no-new: off*/ + (new SCEditor(textarea, options)); + } + }, + instance: function (textarea) { + return textarea._sceditor; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + * @requires jQuery + */ + + // For backwards compatibility + $.sceditor = window.sceditor; + + /** + * Creates an instance of sceditor on all textareas + * matched by the jQuery selector. + * + * If options is set to "state" it will return bool value + * indicating if the editor has been initialised on the + * matched textarea(s). If there is only one textarea + * it will return the bool value for that textarea. + * If more than one textarea is matched it will + * return an array of bool values for each textarea. + * + * If options is set to "instance" it will return the + * current editor instance for the textarea(s). Like the + * state option, if only one textarea is matched this will + * return just the instance for that textarea. If more than + * one textarea is matched it will return an array of + * instances each textarea. + * + * @param {Object|string} [options] Should either be an Object of options or + * the strings "state" or "instance" + * @return {this|Array|Array|SCEditor|boolean} + */ + $.fn.sceditor = function (options) { + var instance; + var ret = []; + + this.each(function () { + instance = this._sceditor; + + // Add state of instance to ret if that is what options is set to + if (options === 'state') { + ret.push(!!instance); + } else if (options === 'instance') { + ret.push(instance); + } else if (!instance) { + $.sceditor.create(this, options); + } + }); + + // If nothing in the ret array then must be init so return this + if (!ret.length) { + return this; + } + + return ret.length === 1 ? ret[0] : ret; + }; + +}(jQuery)); diff --git a/public/assets/development/jquery.sceditor.xhtml.js b/public/assets/development/jquery.sceditor.xhtml.js new file mode 100644 index 0000000..e9dc609 --- /dev/null +++ b/public/assets/development/jquery.sceditor.xhtml.js @@ -0,0 +1,8885 @@ +(function ($) { + 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + + /** + * Check if the passed argument is the + * the passed type. + * + * @param {string} type + * @param {*} arg + * @returns {boolean} + */ + function isTypeof(type, arg) { + return typeof arg === type; + } + + /** + * @type {function(*): boolean} + */ + var isString = isTypeof.bind(null, 'string'); + + /** + * @type {function(*): boolean} + */ + var isUndefined = isTypeof.bind(null, 'undefined'); + + /** + * @type {function(*): boolean} + */ + var isFunction = isTypeof.bind(null, 'function'); + + /** + * @type {function(*): boolean} + */ + var isNumber = isTypeof.bind(null, 'number'); + + + /** + * Returns true if an object has no keys + * + * @param {!Object} obj + * @returns {boolean} + */ + function isEmptyObject(obj) { + return !Object.keys(obj).length; + } + + /** + * Extends the first object with any extra objects passed + * + * If the first argument is boolean and set to true + * it will extend child arrays and objects recursively. + * + * @param {!Object|boolean} targetArg + * @param {...Object} source + * @return {Object} + */ + function extend(targetArg, sourceArg) { + var isTargetBoolean = targetArg === !!targetArg; + var i = isTargetBoolean ? 2 : 1; + var target = isTargetBoolean ? sourceArg : targetArg; + var isDeep = isTargetBoolean ? targetArg : false; + + for (; i < arguments.length; i++) { + var source = arguments[i]; + + // Copy all properties for jQuery compatibility + /* eslint guard-for-in: off */ + for (var key in source) { + var value = source[key]; + + // Skip undefined values to match jQuery and + // skip if target to prevent infinite loop + if (!isUndefined(value)) { + var isObject = value !== null && typeof value === 'object' && + Object.getPrototypeOf(value) === Object.prototype; + var isArray = Array.isArray(value); + + if (isDeep && (isObject || isArray)) { + target[key] = extend( + true, + target[key] || (isArray ? [] : {}), + value + ); + } else { + target[key] = value; + } + } + } + } + + return target; + } + + /** + * Removes an item from the passed array + * + * @param {!Array} arr + * @param {*} item + */ + function arrayRemove(arr, item) { + var i = arr.indexOf(item); + + if (i > -1) { + arr.splice(i, 1); + } + } + + /** + * Iterates over an array or object + * + * @param {!Object|Array} obj + * @param {function(*, *)} fn + */ + function each(obj, fn) { + if (Array.isArray(obj) || 'length' in obj && isNumber(obj.length)) { + for (var i = 0; i < obj.length; i++) { + fn(i, obj[i]); + } + } else { + Object.keys(obj).forEach(function (key) { + fn(key, obj[key]); + }); + } + } + + /** + * Cache of camelCase CSS property names + * @type {Object} + */ + var cssPropertyNameCache = {}; + + /** + * Node type constant for element nodes + * + * @type {number} + */ + var ELEMENT_NODE = 1; + + /** + * Node type constant for text nodes + * + * @type {number} + */ + var TEXT_NODE = 3; + + /** + * Node type constant for comment nodes + * + * @type {number} + */ + + + /** + * Node type document nodes + * + * @type {number} + */ + + + /** + * Node type constant for document fragments + * + * @type {number} + */ + + + function toFloat(value) { + value = parseFloat(value); + + return isFinite(value) ? value : 0; + } + + /** + * Creates an element with the specified attributes + * + * Will create it in the current document unless context + * is specified. + * + * @param {!string} tag + * @param {!Object} [attributes] + * @param {!Document} [context] + * @returns {!HTMLElement} + */ + function createElement(tag, attributes, context) { + var node = (context || document).createElement(tag); + + each(attributes || {}, function (key, value) { + if (key === 'style') { + node.style.cssText = value; + } else if (key in node) { + node[key] = value; + } else { + node.setAttribute(key, value); + } + }); + + return node; + } + + /** + * Returns an array of parents that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {Array} + */ + + + /** + * Gets the first parent node that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {HTMLElement|undefined} + */ + function parent(node, selector) { + var parent = node || {}; + + while ((parent = parent.parentNode) && !/(9|11)/.test(parent.nodeType)) { + if (!selector || is(parent, selector)) { + return parent; + } + } + } + + /** + * Checks the passed node and all parents and + * returns the first matching node if any. + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {HTMLElement|undefined} + */ + function closest(node, selector) { + return is(node, selector) ? node : parent(node, selector); + } + + /** + * Removes the node from the DOM + * + * @param {!HTMLElement} node + */ + function remove(node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + + /** + * Appends child to parent node + * + * @param {!HTMLElement} node + * @param {!HTMLElement} child + */ + function appendChild(node, child) { + node.appendChild(child); + } + + /** + * Finds any child nodes that match the selector + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {NodeList} + */ + function find(node, selector) { + return node.querySelectorAll(selector); + } + + /** + * For on() and off() if to add/remove the event + * to the capture phase + * + * @type {boolean} + */ + var EVENT_CAPTURE = true; + + /** + * For on() and off() if to add/remove the event + * to the bubble phase + * + * @type {boolean} + */ + + + /** + * Adds an event listener for the specified events. + * + * Events should be a space separated list of events. + * + * If selector is specified the handler will only be + * called when the event target matches the selector. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see off() + */ + // eslint-disable-next-line max-params + function on(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector] || function (e) { + var target = e.target; + while (target && target !== node) { + if (is(target, selector)) { + fn.call(target, e); + return; + } + + target = target.parentNode; + } + }; + + fn['_sce-event-' + event + selector] = handler; + } else { + handler = selector; + capture = fn; + } + + node.addEventListener(event, handler, capture || false); + }); + } + + /** + * Removes an event listener for the specified events. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see on() + */ + // eslint-disable-next-line max-params + function off(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector]; + } else { + handler = selector; + capture = fn; + } + + node.removeEventListener(event, handler, capture || false); + }); + } + + /** + * If only attr param is specified it will get + * the value of the attr param. + * + * If value is specified but null the attribute + * will be removed otherwise the attr value will + * be set to the passed value. + * + * @param {!HTMLElement} node + * @param {!string} attr + * @param {?string} [value] + */ + function attr(node, attr, value) { + if (arguments.length < 3) { + return node.getAttribute(attr); + } + + // eslint-disable-next-line eqeqeq, no-eq-null + if (value == null) { + removeAttr(node, attr); + } else { + node.setAttribute(attr, value); + } + } + + /** + * Removes the specified attribute + * + * @param {!HTMLElement} node + * @param {!string} attr + */ + function removeAttr(node, attr) { + node.removeAttribute(attr); + } + + /** + * Sets the passed elements display to none + * + * @param {!HTMLElement} node + */ + function hide(node) { + css(node, 'display', 'none'); + } + + /** + * Sets the passed elements display to default + * + * @param {!HTMLElement} node + */ + function show(node) { + css(node, 'display', ''); + } + + /** + * Toggles an elements visibility + * + * @param {!HTMLElement} node + */ + function toggle(node) { + if (isVisible(node)) { + hide(node); + } else { + show(node); + } + } + + /** + * Gets a computed CSS values or sets an inline CSS value + * + * Rules should be in camelCase format and not + * hyphenated like CSS properties. + * + * @param {!HTMLElement} node + * @param {!Object|string} rule + * @param {string|number} [value] + * @return {string|number|undefined} + */ + function css(node, rule, value) { + if (arguments.length < 3) { + if (isString(rule)) { + return node.nodeType === 1 ? getComputedStyle(node)[rule] : null; + } + + each(rule, function (key, value) { + css(node, key, value); + }); + } else { + // isNaN returns false for null, false and empty strings + // so need to check it's truthy or 0 + var isNumeric = (value || value === 0) && !isNaN(value); + node.style[rule] = isNumeric ? value + 'px' : value; + } + } + + + /** + * Gets or sets thee data attributes on a node + * + * Unlike the jQuery version this only stores data + * in the DOM attributes which means only strings + * can be stored. + * + * @param {Node} node + * @param {string} [key] + * @param {string} [value] + * @return {Object|undefined} + */ + function data(node, key, value) { + var argsLength = arguments.length; + var data = {}; + + if (node.nodeType === ELEMENT_NODE) { + if (argsLength === 1) { + each(node.attributes, function (_, attr) { + if (/^data\-/i.test(attr.name)) { + data[attr.name.substr(5)] = attr.value; + } + }); + + return data; + } + + if (argsLength === 2) { + return attr(node, 'data-' + key); + } + + attr(node, 'data-' + key, String(value)); + } + } + + /** + * Checks if node matches the given selector. + * + * @param {?HTMLElement} node + * @param {string} selector + * @returns {boolean} + */ + function is(node, selector) { + var result = false; + + if (node && node.nodeType === ELEMENT_NODE) { + result = (node.matches || node.msMatchesSelector || + node.webkitMatchesSelector).call(node, selector); + } + + return result; + } + + + /** + * Returns true if node contains child otherwise false. + * + * This differs from the DOM contains() method in that + * if node and child are equal this will return false. + * + * @param {!Node} node + * @param {HTMLElement} child + * @returns {boolean} + */ + function contains(node, child) { + return node !== child && node.contains && node.contains(child); + } + + /** + * @param {Node} node + * @param {string} [selector] + * @returns {?HTMLElement} + */ + function previousElementSibling(node, selector) { + var prev = node.previousElementSibling; + + if (selector && prev) { + return is(prev, selector) ? prev : null; + } + + return prev; + } + + /** + * @param {!Node} node + * @param {!Node} refNode + * @returns {Node} + */ + function insertBefore(node, refNode) { + return refNode.parentNode.insertBefore(node, refNode); + } + + /** + * @param {?HTMLElement} node + * @returns {!Array.} + */ + function classes(node) { + return node.className.trim().split(/\s+/); + } + + /** + * @param {?HTMLElement} node + * @param {string} className + * @returns {boolean} + */ + function hasClass(node, className) { + return is(node, '.' + className); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function addClass(node, className) { + var classList = classes(node); + + if (classList.indexOf(className) < 0) { + classList.push(className); + } + + node.className = classList.join(' '); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function removeClass(node, className) { + var classList = classes(node); + + arrayRemove(classList, className); + + node.className = classList.join(' '); + } + + /** + * Toggles a class on node. + * + * If state is specified and is truthy it will add + * the class. + * + * If state is specified and is falsey it will remove + * the class. + * + * @param {HTMLElement} node + * @param {string} className + * @param {boolean} [state] + */ + function toggleClass(node, className, state) { + state = isUndefined(state) ? !hasClass(node, className) : state; + + if (state) { + addClass(node, className); + } else { + removeClass(node, className); + } + } + + /** + * Gets or sets the width of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function width(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingLeft) + toFloat(cs.paddingRight); + var border = toFloat(cs.borderLeftWidth) + toFloat(cs.borderRightWidth); + + return node.offsetWidth - padding - border; + } + + css(node, 'width', value); + } + + /** + * Gets or sets the height of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function height(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingTop) + toFloat(cs.paddingBottom); + var border = toFloat(cs.borderTopWidth) + toFloat(cs.borderBottomWidth); + + return node.offsetHeight - padding - border; + } + + css(node, 'height', value); + } + + /** + * Triggers a custom event with the specified name and + * sets the detail property to the data object passed. + * + * @param {HTMLElement} node + * @param {string} eventName + * @param {Object} [data] + */ + function trigger(node, eventName, data) { + var event; + + if (isFunction(window.CustomEvent)) { + event = new CustomEvent(eventName, { + bubbles: true, + cancelable: true, + detail: data + }); + } else { + event = node.ownerDocument.createEvent('CustomEvent'); + event.initCustomEvent(eventName, true, true, data); + } + + node.dispatchEvent(event); + } + + /** + * Returns if a node is visible. + * + * @param {HTMLElement} + * @returns {boolean} + */ + function isVisible(node) { + return !!node.getClientRects().length; + } + + /** + * Convert CSS property names into camel case + * + * @param {string} string + * @returns {string} + */ + function camelCase(string) { + return string + .replace(/^-ms-/, 'ms-') + .replace(/-(\w)/g, function (match, char) { + return char.toUpperCase(); + }); + } + + + /** + * Loop all child nodes of the passed node + * + * The function should accept 1 parameter being the node. + * If the function returns false the loop will be exited. + * + * @param {HTMLElement} node + * @param {function} func Callback which is called with every + * child node as the first argument. + * @param {boolean} innermostFirst If the innermost node should be passed + * to the function before it's parents. + * @param {boolean} siblingsOnly If to only traverse the nodes siblings + * @param {boolean} [reverse=false] If to traverse the nodes in reverse + */ + // eslint-disable-next-line max-params + function traverse(node, func, innermostFirst, siblingsOnly, reverse) { + node = reverse ? node.lastChild : node.firstChild; + + while (node) { + var next = reverse ? node.previousSibling : node.nextSibling; + + if ( + (!innermostFirst && func(node) === false) || + (!siblingsOnly && traverse( + node, func, innermostFirst, siblingsOnly, reverse + ) === false) || + (innermostFirst && func(node) === false) + ) { + return false; + } + + node = next; + } + } + + /** + * Like traverse but loops in reverse + * @see traverse + */ + function rTraverse(node, func, innermostFirst, siblingsOnly) { + traverse(node, func, innermostFirst, siblingsOnly, true); + } + + /** + * Parses HTML into a document fragment + * + * @param {string} html + * @param {Document} [context] + * @since 1.4.4 + * @return {DocumentFragment} + */ + function parseHTML(html, context) { + context = context || document; + + var ret = context.createDocumentFragment(); + var tmp = createElement('div', {}, context); + + tmp.innerHTML = html; + + while (tmp.firstChild) { + appendChild(ret, tmp.firstChild); + } + + return ret; + } + + /** + * Checks if an element has any styling. + * + * It has styling if it is not a plain
    or

    or + * if it has a class, style attribute or data. + * + * @param {HTMLElement} elm + * @return {boolean} + * @since 1.4.4 + */ + function hasStyling(node) { + return node && (!is(node, 'p,div') || node.className || + attr(node, 'style') || !isEmptyObject(data(node))); + } + + /** + * Converts an element from one type to another. + * + * For example it can convert the element to + * + * @param {HTMLElement} element + * @param {string} toTagName + * @return {HTMLElement} + * @since 1.4.4 + */ + function convertElement(element, toTagName) { + var newElement = createElement(toTagName, {}, element.ownerDocument); + + each(element.attributes, function (_, attribute) { + // Some browsers parse invalid attributes names like + // 'size"2' which throw an exception when set, just + // ignore these. + try { + attr(newElement, attribute.name, attribute.value); + } catch (ex) {} + }); + + while (element.firstChild) { + appendChild(newElement, element.firstChild); + } + + element.parentNode.replaceChild(newElement, element); + + return newElement; + } + + /** + * List of block level elements separated by bars (|) + * + * @type {string} + */ + var blockLevelList = '|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|' + + 'form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|'; + + /** + * List of elements that do not allow children separated by bars (|) + * + * @param {Node} node + * @return {boolean} + * @since 1.4.5 + */ + function canHaveChildren(node) { + // 1 = Element + // 9 = Document + // 11 = Document Fragment + if (!/11?|9/.test(node.nodeType)) { + return false; + } + + // List of empty HTML tags separated by bar (|) character. + // Source: http://www.w3.org/TR/html4/index/elements.html + // Source: http://www.w3.org/TR/html5/syntax.html#void-elements + return ('|iframe|area|base|basefont|br|col|frame|hr|img|input|wbr' + + '|isindex|link|meta|param|command|embed|keygen|source|track|' + + 'object|').indexOf('|' + node.nodeName.toLowerCase() + '|') < 0; + } + + /** + * Checks if an element is inline + * + * @param {HTMLElement} elm + * @param {boolean} [includeCodeAsBlock=false] + * @return {boolean} + */ + function isInline(elm, includeCodeAsBlock) { + var tagName, + nodeType = (elm || {}).nodeType || TEXT_NODE; + + if (nodeType !== ELEMENT_NODE) { + return nodeType === TEXT_NODE; + } + + tagName = elm.tagName.toLowerCase(); + + if (tagName === 'code') { + return !includeCodeAsBlock; + } + + return blockLevelList.indexOf('|' + tagName + '|') < 0; + } + + /** + * Copy the CSS from 1 node to another. + * + * Only copies CSS defined on the element e.g. style attr. + * + * @param {HTMLElement} from + * @param {HTMLElement} to + */ + function copyCSS(from, to) { + to.style.cssText = from.style.cssText + to.style.cssText; + } + + /** + * Fixes block level elements inside in inline elements. + * + * Also fixes invalid list nesting by placing nested lists + * inside the previous li tag or wrapping them in an li tag. + * + * @param {HTMLElement} node + */ + function fixNesting(node) { + var getLastInlineParent = function (node) { + while (isInline(node.parentNode, true)) { + node = node.parentNode; + } + + return node; + }; + + traverse(node, function (node) { + var list = 'ul,ol', + isBlock = !isInline(node, true); + + // Any blocklevel element inside an inline element needs fixing. + if (isBlock && isInline(node.parentNode, true)) { + var parent = getLastInlineParent(node), + before = extractContents(parent, node), + middle = node; + + // copy current styling so when moved out of the parent + // it still has the same styling + copyCSS(parent, middle); + + insertBefore(before, parent); + insertBefore(middle, parent); + } + + // Fix invalid nested lists which should be wrapped in an li tag + if (isBlock && is(node, list) && is(node.parentNode, list)) { + var li = previousElementSibling(node, 'li'); + + if (!li) { + li = createElement('li'); + insertBefore(li, node); + } + + appendChild(li, node); + } + }); + } + + /** + * Finds the common parent of two nodes + * + * @param {!HTMLElement} node1 + * @param {!HTMLElement} node2 + * @return {?HTMLElement} + */ + function findCommonAncestor(node1, node2) { + while ((node1 = node1.parentNode)) { + if (contains(node1, node2)) { + return node1; + } + } + } + + /** + * @param {?Node} + * @param {boolean} [previous=false] + * @returns {?Node} + */ + function getSibling(node, previous) { + if (!node) { + return null; + } + + return (previous ? node.previousSibling : node.nextSibling) || + getSibling(node.parentNode, previous); + } + + /** + * Removes unused whitespace from the root and all it's children. + * + * @param {!HTMLElement} root + * @since 1.4.3 + */ + function removeWhiteSpace(root) { + var nodeValue, nodeType, next, previous, previousSibling, + nextNode, trimStart, + cssWhiteSpace = css(root, 'whiteSpace'), + // Preserve newlines if is pre-line + preserveNewLines = /line$/i.test(cssWhiteSpace), + node = root.firstChild; + + // Skip pre & pre-wrap with any vendor prefix + if (/pre(\-wrap)?$/i.test(cssWhiteSpace)) { + return; + } + + while (node) { + nextNode = node.nextSibling; + nodeValue = node.nodeValue; + nodeType = node.nodeType; + + if (nodeType === ELEMENT_NODE && node.firstChild) { + removeWhiteSpace(node); + } + + if (nodeType === TEXT_NODE) { + next = getSibling(node); + previous = getSibling(node, true); + trimStart = false; + + while (hasClass(previous, 'sceditor-ignore')) { + previous = getSibling(previous, true); + } + + // If previous sibling isn't inline or is a textnode that + // ends in whitespace, time the start whitespace + if (isInline(node) && previous) { + previousSibling = previous; + + while (previousSibling.lastChild) { + previousSibling = previousSibling.lastChild; + + // eslint-disable-next-line max-depth + while (hasClass(previousSibling, 'sceditor-ignore')) { + previousSibling = getSibling(previousSibling, true); + } + } + + trimStart = previousSibling.nodeType === TEXT_NODE ? + /[\t\n\r ]$/.test(previousSibling.nodeValue) : + !isInline(previousSibling); + } + + // Clear zero width spaces + nodeValue = nodeValue.replace(/\u200B/g, ''); + + // Strip leading whitespace + if (!previous || !isInline(previous) || trimStart) { + nodeValue = nodeValue.replace( + preserveNewLines ? /^[\t ]+/ : /^[\t\n\r ]+/, + '' + ); + } + + // Strip trailing whitespace + if (!next || !isInline(next)) { + nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+$/ : /[\t\n\r ]+$/, + '' + ); + } + + // Remove empty text nodes + if (!nodeValue.length) { + remove(node); + } else { + node.nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+/g : /[\t\n\r ]+/g, + ' ' + ); + } + } + + node = nextNode; + } + } + + /** + * Extracts all the nodes between the start and end nodes + * + * @param {HTMLElement} startNode The node to start extracting at + * @param {HTMLElement} endNode The node to stop extracting at + * @return {DocumentFragment} + */ + function extractContents(startNode, endNode) { + var range = startNode.ownerDocument.createRange(); + + range.setStartBefore(startNode); + range.setEndAfter(endNode); + + return range.extractContents(); + } + + /** + * Gets the offset position of an element + * + * @param {HTMLElement} node + * @return {Object} An object with left and top properties + */ + function getOffset(node) { + var left = 0, + top = 0; + + while (node) { + left += node.offsetLeft; + top += node.offsetTop; + node = node.offsetParent; + } + + return { + left: left, + top: top + }; + } + + /** + * Gets the value of a CSS property from the elements style attribute + * + * @param {HTMLElement} elm + * @param {string} property + * @return {string} + */ + function getStyle(elm, property) { + var direction, styleValue, + elmStyle = elm.style; + + if (!cssPropertyNameCache[property]) { + cssPropertyNameCache[property] = camelCase(property); + } + + property = cssPropertyNameCache[property]; + styleValue = elmStyle[property]; + + // Add an exception for text-align + if ('textAlign' === property) { + direction = elmStyle.direction; + styleValue = styleValue || css(elm, property); + + if (css(elm.parentNode, property) === styleValue || + css(elm, 'display') !== 'block' || is(elm, 'hr,th')) { + return ''; + } + + // IE changes text-align to the same as the current direction + // so skip unless its not the same + if ((/right/i.test(styleValue) && direction === 'rtl') || + (/left/i.test(styleValue) && direction === 'ltr')) { + return ''; + } + } + + return styleValue; + } + + /** + * Tests if an element has a style. + * + * If values are specified it will check that the styles value + * matches one of the values + * + * @param {HTMLElement} elm + * @param {string} property + * @param {string|array} [values] + * @return {boolean} + */ + function hasStyle(elm, property, values) { + var styleValue = getStyle(elm, property); + + if (!styleValue) { + return false; + } + + return !values || styleValue === values || + (Array.isArray(values) && values.indexOf(styleValue) > -1); + } + + /** + * Default options for SCEditor + * @type {Object} + */ + var defaultOptions = { + /** @lends jQuery.sceditor.defaultOptions */ + /** + * Toolbar buttons order and groups. Should be comma separated and + * have a bar | to separate groups + * + * @type {string} + */ + toolbar: 'bold,italic,underline,strike,subscript,superscript|' + + 'left,center,right,justify|font,size,color,removeformat|' + + 'cut,copy,pastetext|bulletlist,orderedlist,indent,outdent|' + + 'table|code,quote|horizontalrule,image,email,link,unlink|' + + 'emoticon,youtube,date,time|ltr,rtl|print,maximize,source', + + /** + * Comma separated list of commands to excludes from the toolbar + * + * @type {string} + */ + toolbarExclude: null, + + /** + * Stylesheet to include in the WYSIWYG editor. This is what will style + * the WYSIWYG elements + * + * @type {string} + */ + style: 'jquery.sceditor.default.css', + + /** + * Comma separated list of fonts for the font selector + * + * @type {string} + */ + fonts: 'Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,' + + 'Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana', + + /** + * Colors should be comma separated and have a bar | to signal a new + * column. + * + * If null the colors will be auto generated. + * + * @type {string} + */ + colors: '#000000,#44B8FF,#1E92F7,#0074D9,#005DC2,#00369B,#b3d5f4|' + + '#444444,#C3FFFF,#9DF9FF,#7FDBFF,#68C4E8,#419DC1,#d9f4ff|' + + '#666666,#72FF84,#4CEA5E,#2ECC40,#17B529,#008E02,#c0f0c6|' + + '#888888,#FFFF44,#FFFA1E,#FFDC00,#E8C500,#C19E00,#fff5b3|' + + '#aaaaaa,#FFC95F,#FFA339,#FF851B,#E86E04,#C14700,#ffdbbb|' + + '#cccccc,#FF857A,#FF5F54,#FF4136,#E82A1F,#C10300,#ffc6c3|' + + '#eeeeee,#FF56FF,#FF30DC,#F012BE,#D900A7,#B20080,#fbb8ec|' + + '#ffffff,#F551FF,#CF2BE7,#B10DC9,#9A00B2,#9A00B2,#e8b6ef', + + /** + * The locale to use. + * @type {string} + */ + locale: attr(document.documentElement, 'lang') || 'en', + + /** + * The Charset to use + * @type {string} + */ + charset: 'utf-8', + + /** + * Compatibility mode for emoticons. + * + * Helps if you have emoticons such as :/ which would put an emoticon + * inside http:// + * + * This mode requires emoticons to be surrounded by whitespace or end of + * line chars. This mode has limited As You Type emoticon conversion + * support. It will not replace AYT for end of line chars, only + * emoticons surrounded by whitespace. They will still be replaced + * correctly when loaded just not AYT. + * + * @type {boolean} + */ + emoticonsCompat: false, + + /** + * If to enable emoticons. Can be changes at runtime using the + * emoticons() method. + * + * @type {boolean} + * @since 1.4.2 + */ + emoticonsEnabled: true, + + /** + * Emoticon root URL + * + * @type {string} + */ + emoticonsRoot: '', + emoticons: { + dropdown: { + ':)': 'emoticons/smile.png', + ':angel:': 'emoticons/angel.png', + ':angry:': 'emoticons/angry.png', + '8-)': 'emoticons/cool.png', + ':\'(': 'emoticons/cwy.png', + ':ermm:': 'emoticons/ermm.png', + ':D': 'emoticons/grin.png', + '<3': 'emoticons/heart.png', + ':(': 'emoticons/sad.png', + ':O': 'emoticons/shocked.png', + ':P': 'emoticons/tongue.png', + ';)': 'emoticons/wink.png' + }, + more: { + ':alien:': 'emoticons/alien.png', + ':blink:': 'emoticons/blink.png', + ':blush:': 'emoticons/blush.png', + ':cheerful:': 'emoticons/cheerful.png', + ':devil:': 'emoticons/devil.png', + ':dizzy:': 'emoticons/dizzy.png', + ':getlost:': 'emoticons/getlost.png', + ':happy:': 'emoticons/happy.png', + ':kissing:': 'emoticons/kissing.png', + ':ninja:': 'emoticons/ninja.png', + ':pinch:': 'emoticons/pinch.png', + ':pouty:': 'emoticons/pouty.png', + ':sick:': 'emoticons/sick.png', + ':sideways:': 'emoticons/sideways.png', + ':silly:': 'emoticons/silly.png', + ':sleeping:': 'emoticons/sleeping.png', + ':unsure:': 'emoticons/unsure.png', + ':woot:': 'emoticons/w00t.png', + ':wassat:': 'emoticons/wassat.png' + }, + hidden: { + ':whistling:': 'emoticons/whistling.png', + ':love:': 'emoticons/wub.png' + } + }, + + /** + * Width of the editor. Set to null for automatic with + * + * @type {?number} + */ + width: null, + + /** + * Height of the editor including toolbar. Set to null for automatic + * height + * + * @type {?number} + */ + height: null, + + /** + * If to allow the editor to be resized + * + * @type {boolean} + */ + resizeEnabled: true, + + /** + * Min resize to width, set to null for half textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinWidth: null, + /** + * Min resize to height, set to null for half textarea height or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinHeight: null, + /** + * Max resize to height, set to null for double textarea height or -1 + * for unlimited + * + * @type {?number} + */ + resizeMaxHeight: null, + /** + * Max resize to width, set to null for double textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMaxWidth: null, + /** + * If resizing by height is enabled + * + * @type {boolean} + */ + resizeHeight: true, + /** + * If resizing by width is enabled + * + * @type {boolean} + */ + resizeWidth: true, + + /** + * Date format, will be overridden if locale specifies one. + * + * The words year, month and day will be replaced with the users current + * year, month and day. + * + * @type {string} + */ + dateFormat: 'year-month-day', + + /** + * Element to inset the toolbar into. + * + * @type {HTMLElement} + */ + toolbarContainer: null, + + /** + * If to enable paste filtering. This is currently experimental, please + * report any issues. + * + * @type {boolean} + */ + enablePasteFiltering: false, + + /** + * If to completely disable pasting into the editor + * + * @type {boolean} + */ + disablePasting: false, + + /** + * If the editor is read only. + * + * @type {boolean} + */ + readOnly: false, + + /** + * If to set the editor to right-to-left mode. + * + * If set to null the direction will be automatically detected. + * + * @type {boolean} + */ + rtl: false, + + /** + * If to auto focus the editor on page load + * + * @type {boolean} + */ + autofocus: false, + + /** + * If to auto focus the editor to the end of the content + * + * @type {boolean} + */ + autofocusEnd: true, + + /** + * If to auto expand the editor to fix the content + * + * @type {boolean} + */ + autoExpand: false, + + /** + * If to auto update original textbox on blur + * + * @type {boolean} + */ + autoUpdate: false, + + /** + * If to enable the browsers built in spell checker + * + * @type {boolean} + */ + spellcheck: true, + + /** + * If to run the source editor when there is no WYSIWYG support. Only + * really applies to mobile OS's. + * + * @type {boolean} + */ + runWithoutWysiwygSupport: false, + + /** + * If to load the editor in source mode and still allow switching + * between WYSIWYG and source mode + * + * @type {boolean} + */ + startInSourceMode: false, + + /** + * Optional ID to give the editor. + * + * @type {string} + */ + id: null, + + /** + * Comma separated list of plugins + * + * @type {string} + */ + plugins: '', + + /** + * z-index to set the editor container to. Needed for jQuery UI dialog. + * + * @type {?number} + */ + zIndex: null, + + /** + * If to trim the BBCode. Removes any spaces at the start and end of the + * BBCode string. + * + * @type {boolean} + */ + bbcodeTrim: false, + + /** + * If to disable removing block level elements by pressing backspace at + * the start of them + * + * @type {boolean} + */ + disableBlockRemove: false, + + /** + * BBCode parser options, only applies if using the editor in BBCode + * mode. + * + * See SCEditor.BBCodeParser.defaults for list of valid options + * + * @type {Object} + */ + parserOptions: { }, + + /** + * CSS that will be added to the to dropdown menu (eg. z-index) + * + * @type {Object} + */ + dropDownCss: { } + }; + + var USER_AGENT = navigator.userAgent; + + /** + * Detects the version of IE is being used if any. + * + * Will be the IE version number or undefined if the + * browser is not IE. + * + * Source: https://gist.github.com/527683 with extra code + * for IE 10 & 11 detection. + * + * @function + * @name ie + * @type {number} + */ + var ie = (function () { + var undef, + v = 3, + doc = document, + div = doc.createElement('div'), + all = div.getElementsByTagName('i'); + + do { + div.innerHTML = ''; + } while (all[0]); + + // Detect IE 10 as it doesn't support conditional comments. + if ((doc.documentMode && doc.all && window.atob)) { + v = 10; + } + + // Detect IE 11 + if (v === 4 && doc.documentMode) { + v = 11; + } + + return v > 4 ? v : undef; + }()); + + var edge = '-ms-ime-align' in document.documentElement.style; + + /** + * Detects if the browser is iOS + * + * Needed to fix iOS specific bugs + * + * @function + * @name ios + * @memberOf jQuery.sceditor + * @type {boolean} + */ + var ios = /iPhone|iPod|iPad| wosbrowser\//i.test(USER_AGENT); + + /** + * If the browser supports WYSIWYG editing (e.g. older mobile browsers). + * + * @function + * @name isWysiwygSupported + * @return {boolean} + */ + var isWysiwygSupported = (function () { + var match, isUnsupported; + + var div = document.createElement('div'); + div.contentEditable = true ; + + // Check if the contentEditable attribute is supported + if (!('contentEditable' in document.documentElement) || + div.contentEditable !== 'true') { + return false; + } + + // I think blackberry supports contentEditable or will at least + // give a valid value for the contentEditable detection above + // so it isn't included in the below tests. + + // I hate having to do UA sniffing but some mobile browsers say they + // support contentediable when it isn't usable, i.e. you can't enter + // text. + // This is the only way I can think of to detect them which is also how + // every other editor I've seen deals with this issue. + + // Exclude Opera mobile and mini + isUnsupported = /Opera Mobi|Opera Mini/i.test(USER_AGENT); + + if (/Android/i.test(USER_AGENT)) { + isUnsupported = true; + + if (/Safari/.test(USER_AGENT)) { + // Android browser 534+ supports content editable + // This also matches Chrome which supports content editable too + match = /Safari\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + } + + // The current version of Amazon Silk supports it, older versions didn't + // As it uses webkit like Android, assume it's the same and started + // working at versions >= 534 + if (/ Silk\//i.test(USER_AGENT)) { + match = /AppleWebKit\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + + // iOS 5+ supports content editable + if (ios) { + // Block any version <= 4_x(_x) + isUnsupported = /OS [0-4](_\d)+ like Mac/i.test(USER_AGENT); + } + + // Firefox does support WYSIWYG on mobiles so override + // any previous value if using FF + if (/Firefox/i.test(USER_AGENT)) { + isUnsupported = false; + } + + if (/OneBrowser/i.test(USER_AGENT)) { + isUnsupported = false; + } + + // UCBrowser works but doesn't give a unique user agent + if (navigator.vendor === 'UCWEB') { + isUnsupported = false; + } + + // IE <= 9 is not supported any more + if (ie <= 9) { + isUnsupported = true; + } + + return !isUnsupported; + }()); + + // Must start with a valid scheme + // ^ + // Schemes that are considered safe + // (https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):| + // Relative schemes (//:) are considered safe + // (\\/\\/)| + // Image data URI's are considered safe + // data:image\\/(png|bmp|gif|p?jpe?g); + var VALID_SCHEME_REGEX = + /^(https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):|(\/\/)|data:image\/(png|bmp|gif|p?jpe?g);/i; + + /** + * Escapes a string so it's safe to use in regex + * + * @param {string} str + * @return {string} + */ + function regex(str) { + return str.replace(/([\-.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + } + + /** + * Escapes all HTML entities in a string + * + * If noQuotes is set to false, all single and double + * quotes will also be escaped + * + * @param {string} str + * @param {boolean} [noQuotes=true] + * @return {string} + * @since 1.4.1 + */ + function entities(str, noQuotes) { + if (!str) { + return str; + } + + var replacements = { + '&': '&', + '<': '<', + '>': '>', + ' ': '  ', + '\r\n': '
    ', + '\r': '
    ', + '\n': '
    ' + }; + + if (noQuotes !== false) { + replacements['"'] = '"'; + replacements['\''] = '''; + replacements['`'] = '`'; + } + + str = str.replace(/ {2}|\r\n|[&<>\r\n'"`]/g, function (match) { + return replacements[match] || match; + }); + + return str; + } + + /** + * Escape URI scheme. + * + * Appends the current URL to a url if it has a scheme that is not: + * + * http + * https + * sftp + * ftp + * mailto + * spotify + * skype + * ssh + * teamspeak + * tel + * // + * data:image/(png|jpeg|jpg|pjpeg|bmp|gif); + * + * **IMPORTANT**: This does not escape any HTML in a url, for + * that use the escape.entities() method. + * + * @param {string} url + * @return {string} + * @since 1.4.5 + */ + function uriScheme(url) { + var path, + // If there is a : before a / then it has a scheme + hasScheme = /^[^\/]*:/i, + location = window.location; + + // Has no scheme or a valid scheme + if ((!url || !hasScheme.test(url)) || VALID_SCHEME_REGEX.test(url)) { + return url; + } + + path = location.pathname.split('/'); + path.pop(); + + return location.protocol + '//' + + location.host + + path.join('/') + '/' + + url; + } + + /** + * HTML templates used by the editor and default commands + * @type {Object} + * @private + */ + var _templates = { + html: + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

    ' + + '', + + toolbarButton: '' + + '
    {dispName}
    ', + + emoticon: '', + + fontOpt: '{font}', + + sizeOpt: '{size}', + + pastetext: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + table: + '
    ' + + '
    ' + + '
    ', + + image: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + email: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + link: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtubeMenu: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtube: + '' + }; + + /** + * Replaces any params in a template with the passed params. + * + * If createHtml is passed it will return a DocumentFragment + * containing the parsed template. + * + * @param {string} name + * @param {Object} [params] + * @param {boolean} [createHtml] + * @returns {string|DocumentFragment} + * @private + */ + function _tmpl (name, params, createHtml) { + var template = _templates[name]; + + Object.keys(params).forEach(function (name) { + template = template.replace( + new RegExp(regex('{' + name + '}'), 'g'), params[name] + ); + }); + + if (createHtml) { + template = parseHTML(template); + } + + return template; + } + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX = ie && ie < 11; + + /** + * Fixes a bug in FF where it sometimes wraps + * new lines in their own list item. + * See issue #359 + */ + function fixFirefoxListBug(editor) { + // Only apply to Firefox as will break other browsers. + if ('mozHidden' in document) { + var node = editor.getBody(); + var next; + + while (node) { + next = node; + + if (next.firstChild) { + next = next.firstChild; + } else { + + while (next && !next.nextSibling) { + next = next.parentNode; + } + + if (next) { + next = next.nextSibling; + } + } + + if (node.nodeType === 3 && /[\n\r\t]+/.test(node.nodeValue)) { + // Only remove if newlines are collapsed + if (!/^pre/.test(css(node.parentNode, 'whiteSpace'))) { + remove(node); + } + } + + node = next; + } + } + } + + + /** + * Map of all the commands for SCEditor + * @type {Object} + * @name commands + * @memberOf jQuery.sceditor + */ + var defaultCmds = { + // START_COMMAND: Bold + bold: { + exec: 'bold', + tooltip: 'Bold', + shortcut: 'Ctrl+B' + }, + // END_COMMAND + // START_COMMAND: Italic + italic: { + exec: 'italic', + tooltip: 'Italic', + shortcut: 'Ctrl+I' + }, + // END_COMMAND + // START_COMMAND: Underline + underline: { + exec: 'underline', + tooltip: 'Underline', + shortcut: 'Ctrl+U' + }, + // END_COMMAND + // START_COMMAND: Strikethrough + strike: { + exec: 'strikethrough', + tooltip: 'Strikethrough' + }, + // END_COMMAND + // START_COMMAND: Subscript + subscript: { + exec: 'subscript', + tooltip: 'Subscript' + }, + // END_COMMAND + // START_COMMAND: Superscript + superscript: { + exec: 'superscript', + tooltip: 'Superscript' + }, + // END_COMMAND + + // START_COMMAND: Left + left: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'left' || align === (isLtr ? 'start' : 'end'); + } + }, + exec: 'justifyleft', + tooltip: 'Align left' + }, + // END_COMMAND + // START_COMMAND: Centre + center: { + exec: 'justifycenter', + tooltip: 'Center' + }, + // END_COMMAND + // START_COMMAND: Right + right: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'right' || align === (isLtr ? 'end' : 'start'); + } + }, + exec: 'justifyright', + tooltip: 'Align right' + }, + // END_COMMAND + // START_COMMAND: Justify + justify: { + exec: 'justifyfull', + tooltip: 'Justify' + }, + // END_COMMAND + + // START_COMMAND: Font + font: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'font')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.opts.fonts.split(',').forEach(function (font) { + appendChild(content, _tmpl('fontOpt', { + font: font + }, true)); + }); + + editor.createDropDown(caller, 'font-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.font._dropDown(editor, caller, function (fontName) { + editor.execCommand('fontname', fontName); + }); + }, + tooltip: 'Font Name' + }, + // END_COMMAND + // START_COMMAND: Size + size: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'size')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + for (var i = 1; i <= 7; i++) { + appendChild(content, _tmpl('sizeOpt', { + size: i + }, true)); + } + + editor.createDropDown(caller, 'fontsize-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.size._dropDown(editor, caller, function (fontSize) { + editor.execCommand('fontsize', fontSize); + }); + }, + tooltip: 'Font Size' + }, + // END_COMMAND + // START_COMMAND: Colour + color: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'), + html = '', + cmd = defaultCmds.color; + + if (!cmd._htmlCache) { + editor.opts.colors.split('|').forEach(function (column) { + html += '
    '; + + column.split(',').forEach(function (color) { + html += + ''; + }); + + html += '
    '; + }); + + cmd._htmlCache = html; + } + + appendChild(content, parseHTML(cmd._htmlCache)); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'color')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'color-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.color._dropDown(editor, caller, function (color) { + editor.execCommand('forecolor', color); + }); + }, + tooltip: 'Font Color' + }, + // END_COMMAND + // START_COMMAND: Remove Format + removeformat: { + exec: 'removeformat', + tooltip: 'Remove Formatting' + }, + // END_COMMAND + + // START_COMMAND: Cut + cut: { + exec: 'cut', + tooltip: 'Cut', + errorMessage: 'Your browser does not allow the cut command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-X' + }, + // END_COMMAND + // START_COMMAND: Copy + copy: { + exec: 'copy', + tooltip: 'Copy', + errorMessage: 'Your browser does not allow the copy command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-C' + }, + // END_COMMAND + // START_COMMAND: Paste + paste: { + exec: 'paste', + tooltip: 'Paste', + errorMessage: 'Your browser does not allow the paste command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-V' + }, + // END_COMMAND + // START_COMMAND: Paste Text + pastetext: { + exec: function (caller) { + var val, + content = createElement('div'), + editor = this; + + appendChild(content, _tmpl('pastetext', { + label: editor._( + 'Paste your text inside the following box:' + ), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + val = find(content, '#txt')[0].value; + + if (val) { + editor.wysiwygEditorInsertText(val); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'pastetext', content); + }, + tooltip: 'Paste Text' + }, + // END_COMMAND + // START_COMMAND: Bullet List + bulletlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertunorderedlist'); + }, + tooltip: 'Bullet list' + }, + // END_COMMAND + // START_COMMAND: Ordered List + orderedlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertorderedlist'); + }, + tooltip: 'Numbered list' + }, + // END_COMMAND + // START_COMMAND: Indent + indent: { + state: function (parent$$1, firstBlock) { + // Only works with lists, for now + var range, startParent, endParent; + + if (is(firstBlock, 'li')) { + return 0; + } + + if (is(firstBlock, 'ul,ol,menu')) { + // if the whole list is selected, then this must be + // invalidated because the browser will place a + //
    there + range = this.getRangeHelper().selectedRange(); + + startParent = range.startContainer.parentNode; + endParent = range.endContainer.parentNode; + + // TODO: could use nodeType for this? + // Maybe just check the firstBlock contains both the start + //and end containers + + // Select the tag, not the textNode + // (that's why the parentNode) + if (startParent !== + startParent.parentNode.firstElementChild || + // work around a bug in FF + (is(endParent, 'li') && endParent !== + endParent.parentNode.lastElementChild)) { + return 0; + } + } + + return -1; + }, + exec: function () { + var editor = this, + block = editor.getRangeHelper().getFirstBlockParent(); + + editor.focus(); + + // An indent system is quite complicated as there are loads + // of complications and issues around how to indent text + // As default, let's just stay with indenting the lists, + // at least, for now. + if (closest(block, 'ul,ol,menu')) { + editor.execCommand('indent'); + } + }, + tooltip: 'Add indent' + }, + // END_COMMAND + // START_COMMAND: Outdent + outdent: { + state: function (parents$$1, firstBlock) { + return closest(firstBlock, 'ul,ol,menu') ? 0 : -1; + }, + exec: function () { + var block = this.getRangeHelper().getFirstBlockParent(); + if (closest(block, 'ul,ol,menu')) { + this.execCommand('outdent'); + } + }, + tooltip: 'Remove one indent' + }, + // END_COMMAND + + // START_COMMAND: Table + table: { + exec: function (caller) { + var editor = this, + content = createElement('div'); + + appendChild(content, _tmpl('table', { + rows: editor._('Rows:'), + cols: editor._('Cols:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var rows = Number(find(content, '#rows')[0].value), + cols = Number(find(content, '#cols')[0].value), + html = ''; + + if (rows > 0 && cols > 0) { + html += Array(rows + 1).join( + '' + + Array(cols + 1).join( + '' + ) + + '' + ); + + html += '
    ' + (IE_BR_FIX ? '' : '
    ') + '
    '; + + editor.wysiwygEditorInsertHtml(html); + editor.closeDropDown(true); + e.preventDefault(); + } + }); + + editor.createDropDown(caller, 'inserttable', content); + }, + tooltip: 'Insert a table' + }, + // END_COMMAND + + // START_COMMAND: Horizontal Rule + horizontalrule: { + exec: 'inserthorizontalrule', + tooltip: 'Insert a horizontal rule' + }, + // END_COMMAND + + // START_COMMAND: Code + code: { + exec: function () { + this.wysiwygEditorInsertHtml( + '', + (IE_BR_FIX ? '' : '
    ') + '
    ' + ); + }, + tooltip: 'Code' + }, + // END_COMMAND + + // START_COMMAND: Image + image: { + _dropDown: function (editor, caller, selected, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('image', { + url: editor._('URL:'), + width: editor._('Width (optional):'), + height: editor._('Height (optional):'), + insert: editor._('Insert') + }, true)); + + + var urlInput = find(content, '#image')[0]; + + urlInput.value = selected; + + on(content, 'click', '.button', function (e) { + if (urlInput.value) { + cb( + urlInput.value, + find(content, '#width')[0].value, + find(content, '#height')[0].value + ); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertimage', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.image._dropDown( + editor, + caller, + '', + function (url, width$$1, height$$1) { + var attrs = ''; + + if (width$$1) { + attrs += ' width="' + width$$1 + '"'; + } + + if (height$$1) { + attrs += ' height="' + height$$1 + '"'; + } + + editor.wysiwygEditorInsertHtml( + '' + ); + } + ); + }, + tooltip: 'Insert an image' + }, + // END_COMMAND + + // START_COMMAND: E-mail + email: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('email', { + label: editor._('E-mail:'), + desc: editor._('Description (optional):'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var email = find(content, '#email')[0].value; + + if (email) { + cb(email, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertemail', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.email._dropDown( + editor, + caller, + function (email, text) { + // needed for IE to reset the last range + editor.focus(); + + if (!editor.getRangeHelper().selectedHtml() || text) { + editor.wysiwygEditorInsertHtml( + '' + + (text || email) + + '' + ); + } else { + editor.execCommand('createlink', 'mailto:' + email); + } + } + ); + }, + tooltip: 'Insert an email' + }, + // END_COMMAND + + // START_COMMAND: Link + link: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('link', { + url: editor._('URL:'), + desc: editor._('Description (optional):'), + ins: editor._('Insert') + }, true)); + + var linkInput = find(content, '#link')[0]; + + function insertUrl(e) { + if (linkInput.value) { + cb(linkInput.value, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + } + + on(content, 'click', '.button', insertUrl); + on(content, 'keypress', function (e) { + // 13 = enter key + if (e.which === 13 && linkInput.value) { + insertUrl(e); + } + }, EVENT_CAPTURE); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.link._dropDown(editor, caller, function (url, text) { + // needed for IE to restore the last range + editor.focus(); + + // If there is no selected text then must set the URL as + // the text. Most browsers do this automatically, sadly + // IE doesn't. + if (text || !editor.getRangeHelper().selectedHtml()) { + text = text || url; + + editor.wysiwygEditorInsertHtml( + '' + text + '' + ); + } else { + editor.execCommand('createlink', url); + } + }); + }, + tooltip: 'Insert a link' + }, + // END_COMMAND + + // START_COMMAND: Unlink + unlink: { + state: function () { + return closest(this.currentNode(), 'a') ? 0 : -1; + }, + exec: function () { + var anchor = closest(this.currentNode(), 'a'); + + if (anchor) { + while (anchor.firstChild) { + insertBefore(anchor.firstChild, anchor); + } + + remove(anchor); + } + }, + tooltip: 'Unlink' + }, + // END_COMMAND + + + // START_COMMAND: Quote + quote: { + exec: function (caller, html, author) { + var before = '
    ', + end = '
    '; + + // if there is HTML passed set end to null so any selected + // text is replaced + if (html) { + author = (author ? '' + author + '' : ''); + before = before + author + html + end; + end = null; + // if not add a newline to the end of the inserted quote + } else if (this.getRangeHelper().selectedHtml() === '') { + end = (IE_BR_FIX ? '' : '
    ') + end; + } + + this.wysiwygEditorInsertHtml(before, end); + }, + tooltip: 'Insert a Quote' + }, + // END_COMMAND + + // START_COMMAND: Emoticons + emoticon: { + exec: function (caller) { + var editor = this; + + var createContent = function (includeMore) { + var moreLink, + opts = editor.opts, + emoticonsRoot = opts.emoticonsRoot || '', + emoticonsCompat = opts.emoticonsCompat, + rangeHelper = editor.getRangeHelper(), + startSpace = emoticonsCompat && + rangeHelper.getOuterText(true, 1) !== ' ' ? ' ' : '', + endSpace = emoticonsCompat && + rangeHelper.getOuterText(false, 1) !== ' ' ? ' ' : '', + content = createElement('div'), + line = createElement('div'), + perLine = 0, + emoticons = extend( + {}, + opts.emoticons.dropdown, + includeMore ? opts.emoticons.more : {} + ); + + appendChild(content, line); + + perLine = Math.sqrt(Object.keys(emoticons).length); + + on(content, 'click', 'img', function (e) { + editor.insert(startSpace + attr(this, 'alt') + endSpace, + null, false).closeDropDown(true); + + e.preventDefault(); + }); + + each(emoticons, function (code, emoticon) { + appendChild(line, createElement('img', { + src: emoticonsRoot + (emoticon.url || emoticon), + alt: code, + title: emoticon.tooltip || code + })); + + if (line.children.length >= perLine) { + line = createElement('div'); + appendChild(content, line); + } + }); + + if (!includeMore && opts.emoticons.more) { + moreLink = createElement('a', { + className: 'sceditor-more' + }); + + appendChild(moreLink, + document.createTextNode(editor._('More'))); + + on(moreLink, 'click', function (e) { + editor.createDropDown( + caller, 'more-emoticons', createContent(true) + ); + + e.preventDefault(); + }); + + appendChild(content, moreLink); + } + + return content; + }; + + editor.createDropDown(caller, 'emoticons', createContent(false)); + }, + txtExec: function (caller) { + defaultCmds.emoticon.exec.call(this, caller); + }, + tooltip: 'Insert an emoticon' + }, + // END_COMMAND + + // START_COMMAND: YouTube + youtube: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + appendChild(content, _tmpl('youtubeMenu', { + label: editor._('Video URL:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var val = find(content, '#link')[0].value; + var idMatch = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/); + var timeMatch = val.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/); + var time = 0; + + if (timeMatch) { + each(timeMatch[1].split(/[hms]/), function (i, val) { + if (val !== '') { + time = (time * 60) + Number(val); + } + }); + } + + if (idMatch && /^[a-zA-Z0-9_\-]{11}$/.test(idMatch[1])) { + callback(idMatch[1], time); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (btn) { + var editor = this; + + defaultCmds.youtube._dropDown(editor, btn, function (id, time) { + editor.wysiwygEditorInsertHtml(_tmpl('youtube', { + id: id, + time: time + })); + }); + }, + tooltip: 'Insert a YouTube video' + }, + // END_COMMAND + + // START_COMMAND: Date + date: { + _date: function (editor) { + var now = new Date(), + year = now.getYear(), + month = now.getMonth() + 1, + day = now.getDate(); + + if (year < 2000) { + year = 1900 + year; + } + + if (month < 10) { + month = '0' + month; + } + + if (day < 10) { + day = '0' + day; + } + + return editor.opts.dateFormat + .replace(/year/i, year) + .replace(/month/i, month) + .replace(/day/i, day); + }, + exec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + txtExec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + tooltip: 'Insert current date' + }, + // END_COMMAND + + // START_COMMAND: Time + time: { + _time: function () { + var now = new Date(), + hours = now.getHours(), + mins = now.getMinutes(), + secs = now.getSeconds(); + + if (hours < 10) { + hours = '0' + hours; + } + + if (mins < 10) { + mins = '0' + mins; + } + + if (secs < 10) { + secs = '0' + secs; + } + + return hours + ':' + mins + ':' + secs; + }, + exec: function () { + this.insertText(defaultCmds.time._time()); + }, + txtExec: function () { + this.insertText(defaultCmds.time._time()); + }, + tooltip: 'Insert current time' + }, + // END_COMMAND + + + // START_COMMAND: Ltr + ltr: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'ltr'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'ltr' ? '' : 'ltr'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Left-to-Right' + }, + // END_COMMAND + + // START_COMMAND: Rtl + rtl: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'rtl'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'rtl' ? '' : 'rtl'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Right-to-Left' + }, + // END_COMMAND + + + // START_COMMAND: Print + print: { + exec: 'print', + tooltip: 'Print' + }, + // END_COMMAND + + // START_COMMAND: Maximize + maximize: { + state: function () { + return this.maximize(); + }, + exec: function () { + this.maximize(!this.maximize()); + }, + txtExec: function () { + this.maximize(!this.maximize()); + }, + tooltip: 'Maximize', + shortcut: 'Ctrl+Shift+M' + }, + // END_COMMAND + + // START_COMMAND: Source + source: { + state: function () { + return this.sourceMode(); + }, + exec: function () { + this.toggleSourceMode(); + }, + txtExec: function () { + this.toggleSourceMode(); + }, + tooltip: 'View source', + shortcut: 'Ctrl+Shift+S' + }, + // END_COMMAND + + // this is here so that commands above can be removed + // without having to remove the , after the last one. + // Needed for IE. + ignore: {} + }; + + var plugins = {}; + + /** + * Plugin Manager class + * @class PluginManager + * @name PluginManager + */ + function PluginManager(thisObj) { + /** + * Alias of this + * + * @private + * @type {Object} + */ + var base = this; + + /** + * Array of all currently registered plugins + * + * @type {Array} + * @private + */ + var registeredPlugins = []; + + + /** + * Changes a signals name from "name" into "signalName". + * + * @param {string} signal + * @return {string} + * @private + */ + var formatSignalName = function (signal) { + return 'signal' + signal.charAt(0).toUpperCase() + signal.slice(1); + }; + + /** + * Calls handlers for a signal + * + * @see call() + * @see callOnlyFirst() + * @param {Array} args + * @param {boolean} returnAtFirst + * @return {*} + * @private + */ + var callHandlers = function (args, returnAtFirst) { + args = [].slice.call(args); + + var idx, ret, + signal = formatSignalName(args.shift()); + + for (idx = 0; idx < registeredPlugins.length; idx++) { + if (signal in registeredPlugins[idx]) { + ret = registeredPlugins[idx][signal].apply(thisObj, args); + + if (returnAtFirst) { + return ret; + } + } + } + }; + + /** + * Calls all handlers for the passed signal + * + * @param {string} signal + * @param {...string} args + * @function + * @name call + * @memberOf PluginManager.prototype + */ + base.call = function () { + callHandlers(arguments, false); + }; + + /** + * Calls the first handler for a signal, and returns the + * + * @param {string} signal + * @param {...string} args + * @return {*} The result of calling the handler + * @function + * @name callOnlyFirst + * @memberOf PluginManager.prototype + */ + base.callOnlyFirst = function () { + return callHandlers(arguments, true); + }; + + /** + * Checks if a signal has a handler + * + * @param {string} signal + * @return {boolean} + * @function + * @name hasHandler + * @memberOf PluginManager.prototype + */ + base.hasHandler = function (signal) { + var i = registeredPlugins.length; + signal = formatSignalName(signal); + + while (i--) { + if (signal in registeredPlugins[i]) { + return true; + } + } + + return false; + }; + + /** + * Checks if the plugin exists in plugins + * + * @param {string} plugin + * @return {boolean} + * @function + * @name exists + * @memberOf PluginManager.prototype + */ + base.exists = function (plugin) { + if (plugin in plugins) { + plugin = plugins[plugin]; + + return typeof plugin === 'function' && + typeof plugin.prototype === 'object'; + } + + return false; + }; + + /** + * Checks if the passed plugin is currently registered. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name isRegistered + * @memberOf PluginManager.prototype + */ + base.isRegistered = function (plugin) { + if (base.exists(plugin)) { + var idx = registeredPlugins.length; + + while (idx--) { + if (registeredPlugins[idx] instanceof plugins[plugin]) { + return true; + } + } + } + + return false; + }; + + /** + * Registers a plugin to receive signals + * + * @param {string} plugin + * @return {boolean} + * @function + * @name register + * @memberOf PluginManager.prototype + */ + base.register = function (plugin) { + if (!base.exists(plugin) || base.isRegistered(plugin)) { + return false; + } + + plugin = new plugins[plugin](); + registeredPlugins.push(plugin); + + if ('init' in plugin) { + plugin.init.call(thisObj); + } + + return true; + }; + + /** + * Deregisters a plugin. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name deregister + * @memberOf PluginManager.prototype + */ + base.deregister = function (plugin) { + var removedPlugin, + pluginIdx = registeredPlugins.length, + removed = false; + + if (!base.isRegistered(plugin)) { + return removed; + } + + while (pluginIdx--) { + if (registeredPlugins[pluginIdx] instanceof plugins[plugin]) { + removedPlugin = registeredPlugins.splice(pluginIdx, 1)[0]; + removed = true; + + if ('destroy' in removedPlugin) { + removedPlugin.destroy.call(thisObj); + } + } + } + + return removed; + }; + + /** + * Clears all plugins and removes the owner reference. + * + * Calling any functions on this object after calling + * destroy will cause a JS error. + * + * @name destroy + * @memberOf PluginManager.prototype + */ + base.destroy = function () { + var i = registeredPlugins.length; + + while (i--) { + if ('destroy' in registeredPlugins[i]) { + registeredPlugins[i].destroy.call(thisObj); + } + } + + registeredPlugins = []; + thisObj = null; + }; + } + + PluginManager.plugins = plugins; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$1 = ie && ie < 11; + + + /** + * Gets the text, start/end node and offset for + * length chars left or right of the passed node + * at the specified offset. + * + * @param {Node} node + * @param {number} offset + * @param {boolean} isLeft + * @param {number} length + * @return {Object} + * @private + */ + var outerText = function (range, isLeft, length) { + var nodeValue, remaining, start, end, node, + text = '', + next = range.startContainer, + offset = range.startOffset; + + // Handle cases where node is a paragraph and offset + // refers to the index of a text node. + // 3 = text node + if (next && next.nodeType !== 3) { + next = next.childNodes[offset]; + offset = 0; + } + + start = end = offset; + + while (length > text.length && next && next.nodeType === 3) { + nodeValue = next.nodeValue; + remaining = length - text.length; + + // If not the first node, start and end should be at their + // max values as will be updated when getting the text + if (node) { + end = nodeValue.length; + start = 0; + } + + node = next; + + if (isLeft) { + start = Math.max(end - remaining, 0); + offset = start; + + text = nodeValue.substr(start, end - start) + text; + next = node.previousSibling; + } else { + end = Math.min(remaining, nodeValue.length); + offset = start + end; + + text += nodeValue.substr(start, end); + next = node.nextSibling; + } + } + + return { + node: node || next, + offset: offset, + text: text + }; + }; + + /** + * Range helper + * + * @class RangeHelper + * @name RangeHelper + */ + function RangeHelper(win, d) { + var _createMarker, _prepareInput, + doc = d || win.contentDocument || win.document, + startMarker = 'sceditor-start-marker', + endMarker = 'sceditor-end-marker', + base = this; + + /** + * Inserts HTML into the current range replacing any selected + * text. + * + * If endHTML is specified the selected contents will be put between + * html and endHTML. If there is nothing selected html and endHTML are + * just concatenate together. + * + * @param {string} html + * @param {string} [endHTML] + * @return False on fail + * @function + * @name insertHTML + * @memberOf RangeHelper.prototype + */ + base.insertHTML = function (html, endHTML) { + var node, div, + range = base.selectedRange(); + + if (!range) { + return false; + } + + if (endHTML) { + html += base.selectedHtml() + endHTML; + } + + div = createElement('p', {}, doc); + node = doc.createDocumentFragment(); + div.innerHTML = html; + + while (div.firstChild) { + appendChild(node, div.firstChild); + } + + base.insertNode(node); + }; + + /** + * Prepares HTML to be inserted by adding a zero width space + * if the last child is empty and adding the range start/end + * markers to the last child. + * + * @param {Node|string} node + * @param {Node|string} [endNode] + * @param {boolean} [returnHtml] + * @return {Node|string} + * @private + */ + _prepareInput = function (node, endNode, returnHtml) { + var lastChild, + frag = doc.createDocumentFragment(); + + if (typeof node === 'string') { + if (endNode) { + node += base.selectedHtml() + endNode; + } + + frag = parseHTML(node); + } else { + appendChild(frag, node); + + if (endNode) { + appendChild(frag, base.selectedRange().extractContents()); + appendChild(frag, endNode); + } + } + + if (!(lastChild = frag.lastChild)) { + return; + } + + while (!isInline(lastChild.lastChild, true)) { + lastChild = lastChild.lastChild; + } + + if (canHaveChildren(lastChild)) { + // Webkit won't allow the cursor to be placed inside an + // empty tag, so add a zero width space to it. + if (!lastChild.lastChild) { + appendChild(lastChild, document.createTextNode('\u200B')); + } + } else { + lastChild = frag; + } + + base.removeMarkers(); + + // Append marks to last child so when restored cursor will be in + // the right place + appendChild(lastChild, _createMarker(startMarker)); + appendChild(lastChild, _createMarker(endMarker)); + + if (returnHtml) { + var div = createElement('div'); + appendChild(div, frag); + + return div.innerHTML; + } + + return frag; + }; + + /** + * The same as insertHTML except with DOM nodes instead + * + * Warning: the nodes must belong to the + * document they are being inserted into. Some browsers + * will throw exceptions if they don't. + * + * Returns boolean false on fail + * + * @param {Node} node + * @param {Node} endNode + * @return {false|undefined} + * @function + * @name insertNode + * @memberOf RangeHelper.prototype + */ + base.insertNode = function (node, endNode) { + var input = _prepareInput(node, endNode), + range = base.selectedRange(), + parent$$1 = range.commonAncestorContainer; + + if (!input) { + return false; + } + + range.deleteContents(); + + // FF allows
    to be selected but inserting a node + // into
    will cause it not to be displayed so must + // insert before the
    in FF. + // 3 = TextNode + if (parent$$1 && parent$$1.nodeType !== 3 && !canHaveChildren(parent$$1)) { + insertBefore(input, parent$$1); + } else { + range.insertNode(input); + } + + base.restoreRange(); + }; + + /** + * Clones the selected Range + * + * @return {Range} + * @function + * @name cloneSelected + * @memberOf RangeHelper.prototype + */ + base.cloneSelected = function () { + var range = base.selectedRange(); + + if (range) { + return range.cloneRange(); + } + }; + + /** + * Gets the selected Range + * + * @return {Range} + * @function + * @name selectedRange + * @memberOf RangeHelper.prototype + */ + base.selectedRange = function () { + var range, firstChild, + sel = win.getSelection(); + + if (!sel) { + return; + } + + // When creating a new range, set the start to the first child + // element of the body element to avoid errors in FF. + if (sel.rangeCount <= 0) { + firstChild = doc.body; + while (firstChild.firstChild) { + firstChild = firstChild.firstChild; + } + + range = doc.createRange(); + // Must be setStartBefore otherwise it can cause infinite + // loops with lists in WebKit. See issue 442 + range.setStartBefore(firstChild); + + sel.addRange(range); + } + + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } + + return range; + }; + + /** + * Gets if there is currently a selection + * + * @return {boolean} + * @function + * @name hasSelection + * @since 1.4.4 + * @memberOf RangeHelper.prototype + */ + base.hasSelection = function () { + var sel = win.getSelection(); + + return sel && sel.rangeCount > 0; + }; + + /** + * Gets the currently selected HTML + * + * @return {string} + * @function + * @name selectedHtml + * @memberOf RangeHelper.prototype + */ + base.selectedHtml = function () { + var div, + range = base.selectedRange(); + + if (range) { + div = createElement('p', {}, doc); + appendChild(div, range.cloneContents()); + + return div.innerHTML; + } + + return ''; + }; + + /** + * Gets the parent node of the selected contents in the range + * + * @return {HTMLElement} + * @function + * @name parentNode + * @memberOf RangeHelper.prototype + */ + base.parentNode = function () { + var range = base.selectedRange(); + + if (range) { + return range.commonAncestorContainer; + } + }; + + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @return {HTMLElement} + * @function + * @name getFirstBlockParent + * @memberOf RangeHelper.prototype + */ + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @param {Node} [n] The element to get the first block level parent from + * @return {HTMLElement} + * @function + * @name getFirstBlockParent^2 + * @since 1.4.1 + * @memberOf RangeHelper.prototype + */ + base.getFirstBlockParent = function (node) { + var func = function (elm) { + if (!isInline(elm, true)) { + return elm; + } + + elm = elm ? elm.parentNode : null; + + return elm ? func(elm) : elm; + }; + + return func(node || base.parentNode()); + }; + + /** + * Inserts a node at either the start or end of the current selection + * + * @param {Bool} start + * @param {Node} node + * @function + * @name insertNodeAt + * @memberOf RangeHelper.prototype + */ + base.insertNodeAt = function (start, node) { + var currentRange = base.selectedRange(), + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(start); + range.insertNode(node); + + // Reselect the current range. + // Fixes issue with Chrome losing the selection. Issue#82 + base.selectRange(currentRange); + }; + + /** + * Creates a marker node + * + * @param {string} id + * @return {HTMLSpanElement} + * @private + */ + _createMarker = function (id) { + base.removeMarker(id); + + var marker = createElement('span', { + id: id, + className: 'sceditor-selection sceditor-ignore', + style: 'display:none;line-height:0' + }, doc); + + marker.innerHTML = ' '; + + return marker; + }; + + /** + * Inserts start/end markers for the current selection + * which can be used by restoreRange to re-select the + * range. + * + * @memberOf RangeHelper.prototype + * @function + * @name insertMarkers + */ + base.insertMarkers = function () { + var currentRange = base.selectedRange(); + var startNode = _createMarker(startMarker); + + base.removeMarkers(); + base.insertNodeAt(true, startNode); + + // Fixes issue with end marker sometimes being placed before + // the start marker when the range is collapsed. + if (currentRange && currentRange.collapsed) { + startNode.parentNode.insertBefore( + _createMarker(endMarker), startNode.nextSibling); + } else { + base.insertNodeAt(false, _createMarker(endMarker)); + } + }; + + /** + * Gets the marker with the specified ID + * + * @param {string} id + * @return {Node} + * @function + * @name getMarker + * @memberOf RangeHelper.prototype + */ + base.getMarker = function (id) { + return doc.getElementById(id); + }; + + /** + * Removes the marker with the specified ID + * + * @param {string} id + * @function + * @name removeMarker + * @memberOf RangeHelper.prototype + */ + base.removeMarker = function (id) { + var marker = base.getMarker(id); + + if (marker) { + remove(marker); + } + }; + + /** + * Removes the start/end markers + * + * @function + * @name removeMarkers + * @memberOf RangeHelper.prototype + */ + base.removeMarkers = function () { + base.removeMarker(startMarker); + base.removeMarker(endMarker); + }; + + /** + * Saves the current range location. Alias of insertMarkers() + * + * @function + * @name saveRage + * @memberOf RangeHelper.prototype + */ + base.saveRange = function () { + base.insertMarkers(); + }; + + /** + * Select the specified range + * + * @param {Range} range + * @function + * @name selectRange + * @memberOf RangeHelper.prototype + */ + base.selectRange = function (range) { + var lastChild; + var sel = win.getSelection(); + var container = range.endContainer; + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$1 && range.collapsed && container && + !isInline(container, true)) { + + lastChild = container.lastChild; + while (lastChild && is(lastChild, '.sceditor-ignore')) { + lastChild = lastChild.previousSibling; + } + + if (is(lastChild, 'br')) { + var rng = doc.createRange(); + rng.setEndAfter(lastChild); + rng.collapse(false); + + if (base.compare(range, rng)) { + range.setStartBefore(lastChild); + range.collapse(true); + } + } + } + + if (sel) { + base.clear(); + sel.addRange(range); + } + }; + + /** + * Restores the last range saved by saveRange() or insertMarkers() + * + * @function + * @name restoreRange + * @memberOf RangeHelper.prototype + */ + base.restoreRange = function () { + var isCollapsed, + range = base.selectedRange(), + start = base.getMarker(startMarker), + end = base.getMarker(endMarker); + + if (!start || !end || !range) { + return false; + } + + isCollapsed = start.nextSibling === end; + + range = doc.createRange(); + range.setStartBefore(start); + range.setEndAfter(end); + + if (isCollapsed) { + range.collapse(true); + } + + base.selectRange(range); + base.removeMarkers(); + }; + + /** + * Selects the text left and right of the current selection + * + * @param {number} left + * @param {number} right + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.selectOuterText = function (left, right) { + var start, end, + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(false); + + start = outerText(range, true, left); + end = outerText(range, false, right); + + range.setStart(start.node, start.offset); + range.setEnd(end.node, end.offset); + + base.selectRange(range); + }; + + /** + * Gets the text left or right of the current selection + * + * @param {boolean} before + * @param {number} length + * @return {string} + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.getOuterText = function (before, length) { + var range = base.cloneSelected(); + + if (!range) { + return ''; + } + + range.collapse(!before); + + return outerText(range, before, length).text; + }; + + /** + * Replaces keywords with values based on the current caret position + * + * @param {Array} keywords + * @param {boolean} includeAfter If to include the text after the + * current caret position or just + * text before + * @param {boolean} keywordsSorted If the keywords array is pre + * sorted shortest to longest + * @param {number} longestKeyword Length of the longest keyword + * @param {boolean} requireWhitespace If the key must be surrounded + * by whitespace + * @param {string} keypressChar If this is being called from + * a keypress event, this should be + * set to the pressed character + * @return {boolean} + * @function + * @name replaceKeyword + * @memberOf RangeHelper.prototype + */ + // eslint-disable-next-line max-params + base.replaceKeyword = function ( + keywords, + includeAfter, + keywordsSorted, + longestKeyword, + requireWhitespace, + keypressChar + ) { + if (!keywordsSorted) { + keywords.sort(function (a, b) { + return a[0].length - b[0].length; + }); + } + + var outerText, match, matchPos, startIndex, + leftLen, charsLeft, keyword, keywordLen, + whitespaceRegex = '(^|[\\s\xA0\u2002\u2003\u2009])', + keywordIdx = keywords.length, + whitespaceLen = requireWhitespace ? 1 : 0, + maxKeyLen = longestKeyword || + keywords[keywordIdx - 1][0].length; + + if (requireWhitespace) { + maxKeyLen++; + } + + keypressChar = keypressChar || ''; + outerText = base.getOuterText(true, maxKeyLen); + leftLen = outerText.length; + outerText += keypressChar; + + if (includeAfter) { + outerText += base.getOuterText(false, maxKeyLen); + } + + while (keywordIdx--) { + keyword = keywords[keywordIdx][0]; + keywordLen = keyword.length; + startIndex = Math.max(0, leftLen - keywordLen - whitespaceLen); + matchPos = -1; + + if (requireWhitespace) { + match = outerText + .substr(startIndex) + .match(new RegExp(whitespaceRegex + + regex(keyword) + whitespaceRegex)); + + if (match) { + // Add the length of the text that was removed by + // substr() and also add 1 for the whitespace + matchPos = match.index + startIndex + match[1].length; + } + } else { + matchPos = outerText.indexOf(keyword, startIndex); + } + + if (matchPos > -1) { + // Make sure the match is between before and + // after, not just entirely in one side or the other + if (matchPos <= leftLen && + matchPos + keywordLen + whitespaceLen >= leftLen) { + charsLeft = leftLen - matchPos; + + // If the keypress char is white space then it should + // not be replaced, only chars that are part of the + // key should be replaced. + base.selectOuterText( + charsLeft, + keywordLen - charsLeft - + (/^\S/.test(keypressChar) ? 1 : 0) + ); + + base.insertHTML(keywords[keywordIdx][1]); + return true; + } + } + } + + return false; + }; + + /** + * Compares two ranges. + * + * If rangeB is undefined it will be set to + * the current selected range + * + * @param {Range} rngA + * @param {Range} [rngB] + * @return {boolean} + * @function + * @name compare + * @memberOf RangeHelper.prototype + */ + base.compare = function (rngA, rngB) { + if (!rngB) { + rngB = base.selectedRange(); + } + + if (!rngA || !rngB) { + return !rngA && !rngB; + } + + return rngA.compareBoundaryPoints(Range.END_TO_END, rngB) === 0 && + rngA.compareBoundaryPoints(Range.START_TO_START, rngB) === 0; + }; + + /** + * Removes any current selection + * + * @since 1.4.6 + * @function + * @name clear + * @memberOf RangeHelper.prototype + */ + base.clear = function () { + var sel = win.getSelection(); + + if (sel) { + if (sel.removeAllRanges) { + sel.removeAllRanges(); + } else if (sel.empty) { + sel.empty(); + } + } + }; + } + + /** + * Checks all emoticons are surrounded by whitespace and + * replaces any that aren't with with their emoticon code. + * + * @param {HTMLElement} node + * @param {rangeHelper} rangeHelper + * @return {void} + */ + function checkWhitespace(node, rangeHelper) { + var noneWsRegex = /[^\s\xA0\u2002\u2003\u2009\u00a0]+/; + var emoticons = node && find(node, 'img[data-sceditor-emoticon]'); + + if (!node || !emoticons.length) { + return; + } + + for (var i = 0; i < emoticons.length; i++) { + var emoticon = emoticons[i]; + var parent$$1 = emoticon.parentNode; + var prev = emoticon.previousSibling; + var next = emoticon.nextSibling; + + if ((!prev || !noneWsRegex.test(prev.nodeValue.slice(-1))) && + (!next || !noneWsRegex.test((next.nodeValue || '')[0]))) { + continue; + } + + var range = rangeHelper.cloneSelected(); + var rangeStart = -1; + var rangeStartContainer = range.startContainer; + var previousText = prev.nodeValue; + + // For IE's HTMLPhraseElement + if (previousText === null) { + previousText = prev.innerText || ''; + } + + previousText += data(emoticon, 'sceditor-emoticon'); + + // If the cursor is after the removed emoticon, add + // the length of the newly added text to it + if (rangeStartContainer === next) { + rangeStart = previousText.length + range.startOffset; + } + + // If the cursor is set before the next node, set it to + // the end of the new text node + if (rangeStartContainer === node && + node.childNodes[range.startOffset] === next) { + rangeStart = previousText.length; + } + + // If the cursor is set before the removed emoticon, + // just keep it at that position + if (rangeStartContainer === prev) { + rangeStart = range.startOffset; + } + + if (!next || next.nodeType !== TEXT_NODE) { + next = parent$$1.insertBefore( + parent$$1.ownerDocument.createTextNode(''), next + ); + } + + next.insertData(0, previousText); + remove(prev); + remove(emoticon); + + // Need to update the range starting position if it's been modified + if (rangeStart > -1) { + range.setStart(next, rangeStart); + range.collapse(true); + rangeHelper.selectRange(range); + } + } + } + + /** + * Replaces any emoticons inside the root node with images. + * + * emoticons should be an object where the key is the emoticon + * code and the value is the HTML to replace it with. + * + * @param {HTMLElement} root + * @param {Object} emoticons + * @param {boolean} emoticonsCompat + * @return {void} + */ + function replace(root, emoticons, emoticonsCompat) { + var doc = root.ownerDocument; + var space = '(^|\\s|\xA0|\u2002|\u2003|\u2009|$)'; + var emoticonCodes = []; + var emoticonRegex = {}; + + // TODO: Make this tag configurable. + if (parent(root, 'code')) { + return; + } + + each(emoticons, function (key) { + emoticonRegex[key] = new RegExp(space + regex(key) + space); + emoticonCodes.push(key); + }); + + // Sort keys longest to shortest so that longer keys + // take precedence (avoids bugs with shorter keys partially + // matching longer ones) + emoticonCodes.sort(function (a, b) { + return b.length - a.length; + }); + + (function convert(node) { + node = node.firstChild; + + while (node) { + // TODO: Make this tag configurable. + if (node.nodeType === ELEMENT_NODE && !is(node, 'code')) { + convert(node); + } + + if (node.nodeType === TEXT_NODE) { + for (var i = 0; i < emoticonCodes.length; i++) { + var text = node.nodeValue; + var key = emoticonCodes[i]; + var index = emoticonsCompat ? + text.search(emoticonRegex[key]) : + text.indexOf(key); + + if (index > -1) { + // When emoticonsCompat is enabled this will be the + // position after any white space + var startIndex = text.indexOf(key, index); + var fragment = parseHTML(emoticons[key], doc); + var after = text.substr(startIndex + key.length); + + fragment.appendChild(doc.createTextNode(after)); + + node.nodeValue = text.substr(0, startIndex); + node.parentNode + .insertBefore(fragment, node.nextSibling); + } + } + } + + node = node.nextSibling; + } + }(root)); + } + + var globalWin = window; + var globalDoc = document; + + var IE_VER = ie; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$2 = IE_VER && IE_VER < 11; + + var IMAGE_MIME_REGEX = /^image\/(p?jpe?g|gif|png|bmp)$/i; + + /** + * Wrap inlines that are in the root in paragraphs. + * + * @param {HTMLBodyElement} body + * @param {Document} doc + * @private + */ + function wrapInlines(body, doc) { + var wrapper; + + traverse(body, function (node) { + if (isInline(node, true)) { + if (!wrapper) { + wrapper = createElement('p', {}, doc); + insertBefore(wrapper, node); + } + + if (node.nodeType !== TEXT_NODE || node.nodeValue !== '') { + appendChild(wrapper, node); + } + } else { + wrapper = null; + } + }, false, true); + } + + /** + * SCEditor - A lightweight WYSIWYG editor + * + * @param {HTMLTextAreaElement} original The textarea to be converted + * @param {Object} userOptions + * @class SCEditor + * @name SCEditor + */ + function SCEditor(original, userOptions) { + /** + * Alias of this + * + * @private + */ + var base = this; + + /** + * Editor format like BBCode or HTML + */ + var format; + + /** + * The div which contains the editor and toolbar + * + * @type {HTMLDivElement} + * @private + */ + var editorContainer; + + /** + * Map of events handlers bound to this instance. + * + * @type {Object} + * @private + */ + var eventHandlers = {}; + + /** + * The editors toolbar + * + * @type {HTMLDivElement} + * @private + */ + var toolbar; + + /** + * The editors iframe which should be in design mode + * + * @type {HTMLIFrameElement} + * @private + */ + var wysiwygEditor; + + /** + * The editors window + * + * @type {Window} + * @private + */ + var wysiwygWindow; + + /** + * The WYSIWYG editors body element + * + * @type {HTMLBodyElement} + * @private + */ + var wysiwygBody; + + /** + * The WYSIWYG editors document + * + * @type {Document} + * @private + */ + var wysiwygDocument; + + /** + * The editors textarea for viewing source + * + * @type {HTMLTextAreaElement} + * @private + */ + var sourceEditor; + + /** + * The current dropdown + * + * @type {HTMLDivElement} + * @private + */ + var dropdown; + + /** + * Store the last cursor position. Needed for IE because it forgets + * + * @type {Range} + * @private + */ + var lastRange; + + /** + * If the user is currently composing text via IME + * @type {boolean} + */ + var isComposing; + + /** + * Timer for valueChanged key handler + * @type {number} + */ + var valueChangedKeyUpTimer; + + /** + * The editors locale + * + * @private + */ + var locale; + + /** + * Stores a cache of preloaded images + * + * @private + * @type {Array.} + */ + var preLoadCache = []; + + /** + * The editors rangeHelper instance + * + * @type {RangeHelper} + * @private + */ + var rangeHelper; + + /** + * An array of button state handlers + * + * @type {Array.} + * @private + */ + var btnStateHandlers = []; + + /** + * Plugin manager instance + * + * @type {PluginManager} + * @private + */ + var pluginManager; + + /** + * The current node containing the selection/caret + * + * @type {Node} + * @private + */ + var currentNode; + + /** + * The first block level parent of the current node + * + * @type {node} + * @private + */ + var currentBlockNode; + + /** + * The current node selection/caret + * + * @type {Object} + * @private + */ + var currentSelection; + + /** + * Used to make sure only 1 selection changed + * check is called every 100ms. + * + * Helps improve performance as it is checked a lot. + * + * @type {boolean} + * @private + */ + var isSelectionCheckPending; + + /** + * If content is required (equivalent to the HTML5 required attribute) + * + * @type {boolean} + * @private + */ + var isRequired; + + /** + * The inline CSS style element. Will be undefined + * until css() is called for the first time. + * + * @type {HTMLStyleElement} + * @private + */ + var inlineCss; + + /** + * Object containing a list of shortcut handlers + * + * @type {Object} + * @private + */ + var shortcutHandlers = {}; + + /** + * The min and max heights that autoExpand should stay within + * + * @type {Object} + * @private + */ + var autoExpandBounds; + + /** + * Timeout for the autoExpand function to throttle calls + * + * @private + */ + var autoExpandThrottle; + + /** + * Cache of the current toolbar buttons + * + * @type {Object} + * @private + */ + var toolbarButtons = {}; + + /** + * Last scroll position before maximizing so + * it can be restored when finished. + * + * @type {number} + * @private + */ + var maximizeScrollPosition; + + /** + * Stores the contents while a paste is taking place. + * + * Needed to support browsers that lack clipboard API support. + * + * @type {?DocumentFragment} + * @private + */ + var pasteContentFragment; + + /** + * All the emoticons from dropdown, more and hidden combined + * and with the emoticons root set + * + * @type {!Object} + * @private + */ + var allEmoticons = {}; + + /** + * Current icon set if any + * + * @type {?Object} + * @private + */ + var icons; + + /** + * Private functions + * @private + */ + var init, + replaceEmoticons, + handleCommand, + saveRange, + initEditor, + initPlugins, + initLocale, + initToolBar, + initOptions, + initEvents, + initResize, + initEmoticons, + handlePasteEvt, + handlePasteData, + handleKeyDown, + handleBackSpace, + handleKeyPress, + handleFormReset, + handleMouseDown, + handleComposition, + handleEvent, + handleDocumentClick, + updateToolBar, + updateActiveButtons, + sourceEditorSelectedText, + appendNewLine, + checkSelectionChanged, + checkNodeChanged, + autofocus, + emoticonsKeyPress, + emoticonsCheckWhitespace, + currentStyledBlockNode, + triggerValueChanged, + valueChangedBlur, + valueChangedKeyUp, + autoUpdate, + autoExpand; + + /** + * All the commands supported by the editor + * @name commands + * @memberOf SCEditor.prototype + */ + base.commands = extend(true, {}, (userOptions.commands || defaultCmds)); + + /** + * Options for this editor instance + * @name opts + * @memberOf SCEditor.prototype + */ + var options = base.opts = extend( + true, {}, defaultOptions, userOptions + ); + + // Don't deep extend emoticons (fixes #565) + base.opts.emoticons = userOptions.emoticons || defaultOptions.emoticons; + + /** + * Creates the editor iframe and textarea + * @private + */ + init = function () { + original._sceditor = base; + + // Load locale + if (options.locale && options.locale !== 'en') { + initLocale(); + } + + editorContainer = createElement('div', { + className: 'sceditor-container' + }); + + insertBefore(editorContainer, original); + css(editorContainer, 'z-index', options.zIndex); + + // Add IE version to the container to allow IE specific CSS + // fixes without using CSS hacks or conditional comments + if (IE_VER) { + addClass(editorContainer, 'ie ie' + IE_VER); + } + + isRequired = original.required; + original.required = false; + + var FormatCtor = SCEditor.formats[options.format]; + format = FormatCtor ? new FormatCtor() : {}; + if ('init' in format) { + format.init.call(base); + } + + // create the editor + initPlugins(); + initEmoticons(); + initToolBar(); + initEditor(); + initOptions(); + initEvents(); + + // force into source mode if is a browser that can't handle + // full editing + if (!isWysiwygSupported) { + base.toggleSourceMode(); + } + + updateActiveButtons(); + + var loaded = function () { + off(globalWin, 'load', loaded); + + if (options.autofocus) { + autofocus(); + } + + autoExpand(); + appendNewLine(); + // TODO: use editor doc and window? + pluginManager.call('ready'); + if ('onReady' in format) { + format.onReady.call(base); + } + }; + on(globalWin, 'load', loaded); + if (globalDoc.readyState === 'complete') { + loaded(); + } + }; + + initPlugins = function () { + var plugins = options.plugins; + + plugins = plugins ? plugins.toString().split(',') : []; + pluginManager = new PluginManager(base); + + plugins.forEach(function (plugin) { + pluginManager.register(plugin.trim()); + }); + }; + + /** + * Init the locale variable with the specified locale if possible + * @private + * @return void + */ + initLocale = function () { + var lang; + + locale = SCEditor.locale[options.locale]; + + if (!locale) { + lang = options.locale.split('-'); + locale = SCEditor.locale[lang[0]]; + } + + // Locale DateTime format overrides any specified in the options + if (locale && locale.dateFormat) { + options.dateFormat = locale.dateFormat; + } + }; + + /** + * Creates the editor iframe and textarea + * @private + */ + initEditor = function () { + sourceEditor = createElement('textarea'); + wysiwygEditor = createElement('iframe', { + frameborder: 0, + allowfullscreen: true + }); + + /* This needs to be done right after they are created because, + * for any reason, the user may not want the value to be tinkered + * by any filters. + */ + if (options.startInSourceMode) { + addClass(editorContainer, 'sourceMode'); + hide(wysiwygEditor); + } else { + addClass(editorContainer, 'wysiwygMode'); + hide(sourceEditor); + } + + if (!options.spellcheck) { + attr(editorContainer, 'spellcheck', 'false'); + } + + if (globalWin.location.protocol === 'https:') { + // eslint-disable-next-line no-script-url + attr(wysiwygEditor, 'src', 'javascript:false'); + } + + // Add the editor to the container + appendChild(editorContainer, wysiwygEditor); + appendChild(editorContainer, sourceEditor); + + // TODO: make this optional somehow + base.dimensions( + options.width || width(original), + options.height || height(original) + ); + + // Add IE version class to the HTML element so can apply + // conditional styling without CSS hacks + var className = IE_VER ? 'ie ie' + IE_VER : ''; + // Add ios to HTML so can apply CSS fix to only it + className += ios ? ' ios' : ''; + + wysiwygDocument = wysiwygEditor.contentDocument; + wysiwygDocument.open(); + wysiwygDocument.write(_tmpl('html', { + attrs: ' class="' + className + '"', + spellcheck: options.spellcheck ? '' : 'spellcheck="false"', + charset: options.charset, + style: options.style + })); + wysiwygDocument.close(); + + wysiwygBody = wysiwygDocument.body; + wysiwygWindow = wysiwygEditor.contentWindow; + + base.readOnly(!!options.readOnly); + + // iframe overflow fix for iOS, also fixes an IE issue with the + // editor not getting focus when clicking inside + if (ios || edge || IE_VER) { + height(wysiwygBody, '100%'); + + if (!IE_VER) { + on(wysiwygBody, 'touchend', base.focus); + } + } + + var tabIndex = attr(original, 'tabindex'); + attr(sourceEditor, 'tabindex', tabIndex); + attr(wysiwygEditor, 'tabindex', tabIndex); + + rangeHelper = new RangeHelper(wysiwygWindow); + + // load any textarea value into the editor + hide(original); + base.val(original.value); + + var placeholder = options.placeholder || + attr(original, 'placeholder'); + + if (placeholder) { + sourceEditor.placeholder = placeholder; + attr(wysiwygBody, 'placeholder', placeholder); + } + }; + + /** + * Initialises options + * @private + */ + initOptions = function () { + // auto-update original textbox on blur if option set to true + if (options.autoUpdate) { + on(wysiwygBody, 'blur', autoUpdate); + on(sourceEditor, 'blur', autoUpdate); + } + + if (options.rtl === null) { + options.rtl = css(sourceEditor, 'direction') === 'rtl'; + } + + base.rtl(!!options.rtl); + + if (options.autoExpand) { + // Need to update when images (or anything else) loads + on(wysiwygBody, 'load', autoExpand, EVENT_CAPTURE); + on(wysiwygBody, 'input keyup', autoExpand); + } + + if (options.resizeEnabled) { + initResize(); + } + + attr(editorContainer, 'id', options.id); + base.emoticons(options.emoticonsEnabled); + }; + + /** + * Initialises events + * @private + */ + initEvents = function () { + var form = original.form; + var compositionEvents = 'compositionstart compositionend'; + var eventsToForward = 'keydown keyup keypress focus blur contextmenu'; + var checkSelectionEvents = 'onselectionchange' in wysiwygDocument ? + 'selectionchange' : + 'keyup focus blur contextmenu mouseup touchend click'; + + on(globalDoc, 'click', handleDocumentClick); + + if (form) { + on(form, 'reset', handleFormReset); + on(form, 'submit', base.updateOriginal, EVENT_CAPTURE); + } + + on(wysiwygBody, 'keypress', handleKeyPress); + on(wysiwygBody, 'keydown', handleKeyDown); + on(wysiwygBody, 'keydown', handleBackSpace); + on(wysiwygBody, 'keyup', appendNewLine); + on(wysiwygBody, 'blur', valueChangedBlur); + on(wysiwygBody, 'keyup', valueChangedKeyUp); + on(wysiwygBody, 'paste', handlePasteEvt); + on(wysiwygBody, compositionEvents, handleComposition); + on(wysiwygBody, checkSelectionEvents, checkSelectionChanged); + on(wysiwygBody, eventsToForward, handleEvent); + + if (options.emoticonsCompat && globalWin.getSelection) { + on(wysiwygBody, 'keyup', emoticonsCheckWhitespace); + } + + on(wysiwygBody, 'blur', function () { + if (!base.val()) { + addClass(wysiwygBody, 'placeholder'); + } + }); + + on(wysiwygBody, 'focus', function () { + removeClass(wysiwygBody, 'placeholder'); + }); + + on(sourceEditor, 'blur', valueChangedBlur); + on(sourceEditor, 'keyup', valueChangedKeyUp); + on(sourceEditor, 'keydown', handleKeyDown); + on(sourceEditor, compositionEvents, handleComposition); + on(sourceEditor, eventsToForward, handleEvent); + + on(wysiwygDocument, 'mousedown', handleMouseDown); + on(wysiwygDocument, checkSelectionEvents, checkSelectionChanged); + on(wysiwygDocument, 'beforedeactivate keyup mouseup', saveRange); + on(wysiwygDocument, 'keyup', appendNewLine); + on(wysiwygDocument, 'focus', function () { + lastRange = null; + }); + + on(editorContainer, 'selectionchanged', checkNodeChanged); + on(editorContainer, 'selectionchanged', updateActiveButtons); + // Custom events to forward + on( + editorContainer, + 'selectionchanged valuechanged nodechanged pasteraw paste', + handleEvent + ); + }; + + /** + * Creates the toolbar and appends it to the container + * @private + */ + initToolBar = function () { + var group, + commands = base.commands, + exclude = (options.toolbarExclude || '').split(','), + groups = options.toolbar.split('|'); + + toolbar = createElement('div', { + className: 'sceditor-toolbar', + unselectable: 'on' + }); + + if (options.icons in SCEditor.icons) { + icons = new SCEditor.icons[options.icons](); + } + + each(groups, function (_, menuItems) { + group = createElement('div', { + className: 'sceditor-group' + }); + + each(menuItems.split(','), function (_, commandName) { + var button, shortcut, + command = commands[commandName]; + + // The commandName must be a valid command and not excluded + if (!command || exclude.indexOf(commandName) > -1) { + return; + } + + shortcut = command.shortcut; + button = _tmpl('toolbarButton', { + name: commandName, + dispName: base._(command.name || + command.tooltip || commandName) + }, true).firstChild; + + if (icons && icons.create) { + var icon = icons.create(commandName); + if (icon) { + insertBefore(icons.create(commandName), + button.firstChild); + addClass(button, 'has-icon'); + } + } + + button._sceTxtMode = !!command.txtExec; + button._sceWysiwygMode = !!command.exec; + toggleClass(button, 'disabled', !command.exec); + on(button, 'click', function (e) { + if (!hasClass(button, 'disabled')) { + handleCommand(button, command); + } + + updateActiveButtons(); + e.preventDefault(); + }); + // Prevent editor losing focus when button clicked + on(button, 'mousedown', function (e) { + base.closeDropDown(); + e.preventDefault(); + }); + + if (command.tooltip) { + attr(button, 'title', + base._(command.tooltip) + + (shortcut ? ' (' + shortcut + ')' : '') + ); + } + + if (shortcut) { + base.addShortcut(shortcut, commandName); + } + + if (command.state) { + btnStateHandlers.push({ + name: commandName, + state: command.state + }); + // exec string commands can be passed to queryCommandState + } else if (isString(command.exec)) { + btnStateHandlers.push({ + name: commandName, + state: command.exec + }); + } + + appendChild(group, button); + toolbarButtons[commandName] = button; + }); + + // Exclude empty groups + if (group.firstChild) { + appendChild(toolbar, group); + } + }); + + // Append the toolbar to the toolbarContainer option if given + appendChild(options.toolbarContainer || editorContainer, toolbar); + }; + + /** + * Creates the resizer. + * @private + */ + initResize = function () { + var minHeight, maxHeight, minWidth, maxWidth, + mouseMoveFunc, mouseUpFunc, + grip = createElement('div', { + className: 'sceditor-grip' + }), + // Cover is used to cover the editor iframe so document + // still gets mouse move events + cover = createElement('div', { + className: 'sceditor-resize-cover' + }), + moveEvents = 'touchmove mousemove', + endEvents = 'touchcancel touchend mouseup', + startX = 0, + startY = 0, + newX = 0, + newY = 0, + startWidth = 0, + startHeight = 0, + origWidth = width(editorContainer), + origHeight = height(editorContainer), + isDragging = false, + rtl = base.rtl(); + + minHeight = options.resizeMinHeight || origHeight / 1.5; + maxHeight = options.resizeMaxHeight || origHeight * 2.5; + minWidth = options.resizeMinWidth || origWidth / 1.25; + maxWidth = options.resizeMaxWidth || origWidth * 1.25; + + mouseMoveFunc = function (e) { + // iOS uses window.event + if (e.type === 'touchmove') { + e = globalWin.event; + newX = e.changedTouches[0].pageX; + newY = e.changedTouches[0].pageY; + } else { + newX = e.pageX; + newY = e.pageY; + } + + var newHeight = startHeight + (newY - startY), + newWidth = rtl ? + startWidth - (newX - startX) : + startWidth + (newX - startX); + + if (maxWidth > 0 && newWidth > maxWidth) { + newWidth = maxWidth; + } + if (minWidth > 0 && newWidth < minWidth) { + newWidth = minWidth; + } + if (!options.resizeWidth) { + newWidth = false; + } + + if (maxHeight > 0 && newHeight > maxHeight) { + newHeight = maxHeight; + } + if (minHeight > 0 && newHeight < minHeight) { + newHeight = minHeight; + } + if (!options.resizeHeight) { + newHeight = false; + } + + if (newWidth || newHeight) { + base.dimensions(newWidth, newHeight); + } + + e.preventDefault(); + }; + + mouseUpFunc = function (e) { + if (!isDragging) { + return; + } + + isDragging = false; + + hide(cover); + removeClass(editorContainer, 'resizing'); + off(globalDoc, moveEvents, mouseMoveFunc); + off(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }; + + if (icons && icons.create) { + var icon = icons.create('grip'); + if (icon) { + appendChild(grip, icon); + addClass(grip, 'has-icon'); + } + } + + appendChild(editorContainer, grip); + appendChild(editorContainer, cover); + hide(cover); + + on(grip, 'touchstart mousedown', function (e) { + // iOS uses window.event + if (e.type === 'touchstart') { + e = globalWin.event; + startX = e.touches[0].pageX; + startY = e.touches[0].pageY; + } else { + startX = e.pageX; + startY = e.pageY; + } + + startWidth = width(editorContainer); + startHeight = height(editorContainer); + isDragging = true; + + addClass(editorContainer, 'resizing'); + show(cover); + on(globalDoc, moveEvents, mouseMoveFunc); + on(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }); + }; + + /** + * Prefixes and preloads the emoticon images + * @private + */ + initEmoticons = function () { + var emoticons = options.emoticons; + var root = options.emoticonsRoot || ''; + + if (emoticons) { + allEmoticons = extend( + {}, emoticons.more, emoticons.dropdown, emoticons.hidden + ); + } + + each(allEmoticons, function (key, url) { + allEmoticons[key] = _tmpl('emoticon', { + key: key, + // Prefix emoticon root to emoticon urls + url: root + (url.url || url), + tooltip: url.tooltip || key + }); + + // Preload the emoticon + if (options.emoticonsEnabled) { + preLoadCache.push(createElement('img', { + src: root + (url.url || url) + })); + } + }); + }; + + /** + * Autofocus the editor + * @private + */ + autofocus = function () { + var range, txtPos, + node = wysiwygBody.firstChild, + focusEnd = !!options.autofocusEnd; + + // Can't focus invisible elements + if (!isVisible(editorContainer)) { + return; + } + + if (base.sourceMode()) { + txtPos = focusEnd ? sourceEditor.value.length : 0; + + sourceEditor.setSelectionRange(txtPos, txtPos); + + return; + } + + removeWhiteSpace(wysiwygBody); + + if (focusEnd) { + if (!(node = wysiwygBody.lastChild)) { + node = createElement('p', {}, wysiwygDocument); + appendChild(wysiwygBody, node); + } + + while (node.lastChild) { + node = node.lastChild; + + // IE < 11 should place the cursor after the
    as + // it will show it as a newline. IE >= 11 and all + // other browsers should place the cursor before. + if (!IE_BR_FIX$2 && is(node, 'br') && node.previousSibling) { + node = node.previousSibling; + } + } + } + + range = wysiwygDocument.createRange(); + + if (!canHaveChildren(node)) { + range.setStartBefore(node); + + if (focusEnd) { + range.setStartAfter(node); + } + } else { + range.selectNodeContents(node); + } + + range.collapse(!focusEnd); + rangeHelper.selectRange(range); + currentSelection = range; + + if (focusEnd) { + wysiwygBody.scrollTop = wysiwygBody.scrollHeight; + } + + base.focus(); + }; + + /** + * Gets if the editor is read only + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly + * @return {boolean} + */ + /** + * Sets if the editor is read only + * + * @param {boolean} readOnly + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly^2 + * @return {this} + */ + base.readOnly = function (readOnly) { + if (typeof readOnly !== 'boolean') { + return !sourceEditor.readonly; + } + + wysiwygBody.contentEditable = !readOnly; + sourceEditor.readonly = !readOnly; + + updateToolBar(readOnly); + + return base; + }; + + /** + * Gets if the editor is in RTL mode + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl + * @return {boolean} + */ + /** + * Sets if the editor is in RTL mode + * + * @param {boolean} rtl + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl^2 + * @return {this} + */ + base.rtl = function (rtl) { + var dir = rtl ? 'rtl' : 'ltr'; + + if (typeof rtl !== 'boolean') { + return attr(sourceEditor, 'dir') === 'rtl'; + } + + attr(wysiwygBody, 'dir', dir); + attr(sourceEditor, 'dir', dir); + + removeClass(editorContainer, 'rtl'); + removeClass(editorContainer, 'ltr'); + addClass(editorContainer, dir); + + if (icons && icons.rtl) { + icons.rtl(rtl); + } + + return base; + }; + + /** + * Updates the toolbar to disable/enable the appropriate buttons + * @private + */ + updateToolBar = function (disable) { + var mode = base.inSourceMode() ? '_sceTxtMode' : '_sceWysiwygMode'; + + each(toolbarButtons, function (_, button) { + toggleClass(button, 'disabled', disable || !button[mode]); + }); + }; + + /** + * Gets the width of the editor in pixels + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width + * @return {number} + */ + /** + * Sets the width of the editor + * + * @param {number} width Width in pixels + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width^2 + * @return {this} + */ + /** + * Sets the width of the editor + * + * The saveWidth specifies if to save the width. The stored width can be + * used for things like restoring from maximized state. + * + * @param {number} width Width in pixels + * @param {boolean} [saveWidth=true] If to store the width + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name width^3 + * @return {this} + */ + base.width = function (width$$1, saveWidth) { + if (!width$$1 && width$$1 !== 0) { + return width(editorContainer); + } + + base.dimensions(width$$1, null, saveWidth); + + return base; + }; + + /** + * Returns an object with the properties width and height + * which are the width and height of the editor in px. + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions + * @return {object} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^2 + * @return {this} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + *

    The save argument specifies if to save the new sizes. + * The saved sizes can be used for things like restoring from + * maximized state. This should normally be left as true.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @param {boolean} [save=true] If to store the new sizes + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^3 + * @return {this} + */ + base.dimensions = function (width$$1, height$$1, save) { + // set undefined width/height to boolean false + width$$1 = (!width$$1 && width$$1 !== 0) ? false : width$$1; + height$$1 = (!height$$1 && height$$1 !== 0) ? false : height$$1; + + if (width$$1 === false && height$$1 === false) { + return { width: base.width(), height: base.height() }; + } + + if (width$$1 !== false) { + if (save !== false) { + options.width = width$$1; + } + + width(editorContainer, width$$1); + } + + if (height$$1 !== false) { + if (save !== false) { + options.height = height$$1; + } + + height(editorContainer, height$$1); + } + + return base; + }; + + /** + * Gets the height of the editor in px + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height + * @return {number} + */ + /** + * Sets the height of the editor + * + * @param {number} height Height in px + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height^2 + * @return {this} + */ + /** + * Sets the height of the editor + * + * The saveHeight specifies if to save the height. + * + * The stored height can be used for things like + * restoring from maximized state. + * + * @param {number} height Height in px + * @param {boolean} [saveHeight=true] If to store the height + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name height^3 + * @return {this} + */ + base.height = function (height$$1, saveHeight) { + if (!height$$1 && height$$1 !== 0) { + return height(editorContainer); + } + + base.dimensions(null, height$$1, saveHeight); + + return base; + }; + + /** + * Gets if the editor is maximised or not + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize + * @return {boolean} + */ + /** + * Sets if the editor is maximised or not + * + * @param {boolean} maximize If to maximise the editor + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize^2 + * @return {this} + */ + base.maximize = function (maximize) { + var maximizeSize = 'sceditor-maximize'; + + if (isUndefined(maximize)) { + return hasClass(editorContainer, maximizeSize); + } + + maximize = !!maximize; + + if (maximize) { + maximizeScrollPosition = globalWin.pageYOffset; + } + + toggleClass(globalDoc.documentElement, maximizeSize, maximize); + toggleClass(globalDoc.body, maximizeSize, maximize); + toggleClass(editorContainer, maximizeSize, maximize); + base.width(maximize ? '100%' : options.width, false); + base.height(maximize ? '100%' : options.height, false); + + if (!maximize) { + globalWin.scrollTo(0, maximizeScrollPosition); + } + + autoExpand(); + + return base; + }; + + autoExpand = function () { + if (options.autoExpand && !autoExpandThrottle) { + autoExpandThrottle = setTimeout(base.expandToContent, 200); + } + }; + + /** + * Expands or shrinks the editors height to the height of it's content + * + * Unless ignoreMaxHeight is set to true it will not expand + * higher than the maxHeight option. + * + * @since 1.3.5 + * @param {boolean} [ignoreMaxHeight=false] + * @function + * @name expandToContent + * @memberOf SCEditor.prototype + * @see #resizeToContent + */ + base.expandToContent = function (ignoreMaxHeight) { + if (base.maximize()) { + return; + } + + clearTimeout(autoExpandThrottle); + autoExpandThrottle = false; + + if (!autoExpandBounds) { + var height$$1 = options.resizeMinHeight || options.height || + height(original); + + autoExpandBounds = { + min: height$$1, + max: options.resizeMaxHeight || (height$$1 * 2) + }; + } + + var range = globalDoc.createRange(); + range.selectNodeContents(wysiwygBody); + + var rect = range.getBoundingClientRect(); + var current = wysiwygDocument.documentElement.clientHeight - 1; + var spaceNeeded = rect.bottom - rect.top; + var newHeight = base.height() + 1 + (spaceNeeded - current); + + if (!ignoreMaxHeight && autoExpandBounds.max !== -1) { + newHeight = Math.min(newHeight, autoExpandBounds.max); + } + + base.height(Math.ceil(Math.max(newHeight, autoExpandBounds.min))); + }; + + /** + * Destroys the editor, removing all elements and + * event handlers. + * + * Leaves only the original textarea. + * + * @function + * @name destroy + * @memberOf SCEditor.prototype + */ + base.destroy = function () { + // Don't destroy if the editor has already been destroyed + if (!pluginManager) { + return; + } + + pluginManager.destroy(); + + rangeHelper = null; + lastRange = null; + pluginManager = null; + + if (dropdown) { + remove(dropdown); + } + + off(globalDoc, 'click', handleDocumentClick); + + // TODO: make off support null nodes? + var form = original.form; + if (form) { + off(form, 'reset', handleFormReset); + off(form, 'submit', base.updateOriginal); + } + + remove(sourceEditor); + remove(toolbar); + remove(editorContainer); + + delete original._sceditor; + show(original); + + original.required = isRequired; + }; + + + /** + * Creates a menu item drop down + * + * @param {HTMLElement} menuItem The button to align the dropdown with + * @param {string} name Used for styling the dropdown, will be + * a class sceditor-name + * @param {HTMLElement} content The HTML content of the dropdown + * @param {boolean} ieFix If to add the unselectable attribute + * to all the contents elements. Stops + * IE from deselecting the text in the + * editor + * @function + * @name createDropDown + * @memberOf SCEditor.prototype + */ + base.createDropDown = function (menuItem, name, content, ieFix) { + // first click for create second click for close + var dropDownCss, + dropDownClass = 'sceditor-' + name; + + // Will re-focus the editor. This is needed for IE + // as it has special logic to save/restore the selection + base.closeDropDown(true); + + // Only close the dropdown if it was already open + if (dropdown && hasClass(dropdown, dropDownClass)) { + return; + } + + // IE needs unselectable attr to stop it from + // unselecting the text in the editor. + // SCEditor can cope if IE does unselect the + // text it's just not nice. + if (ieFix !== false) { + each(find(content, ':not(input):not(textarea)'), + function (_, node) { + if (node.nodeType === ELEMENT_NODE) { + attr(node, 'unselectable', 'on'); + } + }); + } + + dropDownCss = extend({ + top: menuItem.offsetTop, + left: menuItem.offsetLeft, + marginTop: menuItem.clientHeight + }, options.dropDownCss); + + dropdown = createElement('div', { + className: 'sceditor-dropdown ' + dropDownClass + }); + + css(dropdown, dropDownCss); + appendChild(dropdown, content); + appendChild(editorContainer, dropdown); + on(dropdown, 'click focusin', function (e) { + // stop clicks within the dropdown from being handled + e.stopPropagation(); + }); + + // If try to focus the first input immediately IE will + // place the cursor at the start of the editor instead + // of focusing on the input. + setTimeout(function () { + if (dropdown) { + var first = find(dropdown, 'input,textarea')[0]; + if (first) { + first.focus(); + } + } + }); + }; + + /** + * Handles any document click and closes the dropdown if open + * @private + */ + handleDocumentClick = function (e) { + // ignore right clicks + if (e.which !== 3 && dropdown && !e.defaultPrevented) { + autoUpdate(); + + base.closeDropDown(); + } + }; + + /** + * Handles the WYSIWYG editors paste event + * @private + */ + handlePasteEvt = function (e) { + var isIeOrEdge = IE_VER || edge; + var editable = wysiwygBody; + var clipboard = e.clipboardData; + var loadImage = function (file) { + var reader = new FileReader(); + reader.onload = function (e) { + handlePasteData({ + html: '' + }); + }; + reader.readAsDataURL(file); + }; + + // Modern browsers with clipboard API - everything other than _very_ + // old android web views and UC browser which doesn't support the + // paste event at all. + if (clipboard && !isIeOrEdge) { + var data$$1 = {}; + var types = clipboard.types; + var items = clipboard.items; + + e.preventDefault(); + + for (var i = 0; i < types.length; i++) { + // Normalise image pasting to paste as a data-uri + if (globalWin.FileReader && items && + IMAGE_MIME_REGEX.test(items[i].type)) { + return loadImage(clipboard.items[i].getAsFile()); + } + + data$$1[types[i]] = clipboard.getData(types[i]); + } + // Call plugins here with file? + data$$1.text = data$$1['text/plain']; + data$$1.html = data$$1['text/html']; + + handlePasteData(data$$1); + // If contentsFragment exists then we are already waiting for a + // previous paste so let the handler for that handle this one too + } else if (!pasteContentFragment) { + // Save the scroll position so can be restored + // when contents is restored + var scrollTop = editable.scrollTop; + + rangeHelper.saveRange(); + + pasteContentFragment = globalDoc.createDocumentFragment(); + while (editable.firstChild) { + appendChild(pasteContentFragment, editable.firstChild); + } + + setTimeout(function () { + var html = editable.innerHTML; + + editable.innerHTML = ''; + appendChild(editable, pasteContentFragment); + editable.scrollTop = scrollTop; + pasteContentFragment = false; + + rangeHelper.restoreRange(); + + handlePasteData({ html: html }); + }, 0); + } + }; + + /** + * Gets the pasted data, filters it and then inserts it. + * @param {Object} data + * @private + */ + handlePasteData = function (data$$1) { + var pasteArea = createElement('div', {}, wysiwygDocument); + + pluginManager.call('pasteRaw', data$$1); + trigger(editorContainer, 'pasteraw', data$$1); + + if (data$$1.html) { + pasteArea.innerHTML = data$$1.html; + + // fix any invalid nesting + fixNesting(pasteArea); + } else { + pasteArea.innerHTML = entities(data$$1.text || ''); + } + + var paste = { + val: pasteArea.innerHTML + }; + + if ('fragmentToSource' in format) { + paste.val = format + .fragmentToSource(paste.val, wysiwygDocument, currentNode); + } + + pluginManager.call('paste', paste); + trigger(editorContainer, 'paste', paste); + + if ('fragmentToHtml' in format) { + paste.val = format + .fragmentToHtml(paste.val, currentNode); + } + + pluginManager.call('pasteHtml', paste); + + base.wysiwygEditorInsertHtml(paste.val, null, true); + }; + + /** + * Closes any currently open drop down + * + * @param {boolean} [focus=false] If to focus the editor + * after closing the drop down + * @function + * @name closeDropDown + * @memberOf SCEditor.prototype + */ + base.closeDropDown = function (focus) { + if (dropdown) { + remove(dropdown); + dropdown = null; + } + + if (focus === true) { + base.focus(); + } + }; + + + /** + * Inserts HTML into WYSIWYG editor. + * + * If endHtml is specified, any selected text will be placed + * between html and endHtml. If there is no selected text html + * and endHtml will just be concatenate together. + * + * @param {string} html + * @param {string} [endHtml=null] + * @param {boolean} [overrideCodeBlocking=false] If to insert the html + * into code tags, by + * default code tags only + * support text. + * @function + * @name wysiwygEditorInsertHtml + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertHtml = function ( + html, endHtml, overrideCodeBlocking + ) { + var marker, scrollTop, scrollTo, + editorHeight = height(wysiwygEditor); + + base.focus(); + + // TODO: This code tag should be configurable and + // should maybe convert the HTML into text instead + // Don't apply to code elements + if (!overrideCodeBlocking && closest(currentBlockNode, 'code')) { + return; + } + + // Insert the HTML and save the range so the editor can be scrolled + // to the end of the selection. Also allows emoticons to be replaced + // without affecting the cursor position + rangeHelper.insertHTML(html, endHtml); + rangeHelper.saveRange(); + replaceEmoticons(); + + // Scroll the editor after the end of the selection + marker = find(wysiwygBody, '#sceditor-end-marker')[0]; + show(marker); + scrollTop = wysiwygBody.scrollTop; + scrollTo = (getOffset(marker).top + + (marker.offsetHeight * 1.5)) - editorHeight; + hide(marker); + + // Only scroll if marker isn't already visible + if (scrollTo > scrollTop || scrollTo + editorHeight < scrollTop) { + wysiwygBody.scrollTop = scrollTo; + } + + triggerValueChanged(false); + rangeHelper.restoreRange(); + + // Add a new line after the last block element + // so can always add text after it + appendNewLine(); + }; + + /** + * Like wysiwygEditorInsertHtml except it will convert any HTML + * into text before inserting it. + * + * @param {string} text + * @param {string} [endText=null] + * @function + * @name wysiwygEditorInsertText + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertText = function (text, endText) { + base.wysiwygEditorInsertHtml( + entities(text), entities(endText) + ); + }; + + /** + * Inserts text into the WYSIWYG or source editor depending on which + * mode the editor is in. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.3.5 + * @function + * @name insertText + * @memberOf SCEditor.prototype + */ + base.insertText = function (text, endText) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(text, endText); + } else { + base.wysiwygEditorInsertText(text, endText); + } + + return base; + }; + + /** + * Like wysiwygEditorInsertHtml but inserts text into the + * source mode editor instead. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * The cursor will be placed after the text param. If endText is + * specified the cursor will be placed before endText, so passing:
    + * + * '[b]', '[/b]' + * + * Would cause the cursor to be placed:
    + * + * [b]Selected text|[/b] + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.4.0 + * @function + * @name sourceEditorInsertText + * @memberOf SCEditor.prototype + */ + base.sourceEditorInsertText = function (text, endText) { + var scrollTop, currentValue, + startPos = sourceEditor.selectionStart, + endPos = sourceEditor.selectionEnd; + + scrollTop = sourceEditor.scrollTop; + sourceEditor.focus(); + currentValue = sourceEditor.value; + + if (endText) { + text += currentValue.substring(startPos, endPos) + endText; + } + + sourceEditor.value = currentValue.substring(0, startPos) + + text + + currentValue.substring(endPos, currentValue.length); + + sourceEditor.selectionStart = (startPos + text.length) - + (endText ? endText.length : 0); + sourceEditor.selectionEnd = sourceEditor.selectionStart; + + sourceEditor.scrollTop = scrollTop; + sourceEditor.focus(); + + triggerValueChanged(); + }; + + /** + * Gets the current instance of the rangeHelper class + * for the editor. + * + * @return {RangeHelper} + * @function + * @name getRangeHelper + * @memberOf SCEditor.prototype + */ + base.getRangeHelper = function () { + return rangeHelper; + }; + + /** + * Gets or sets the source editor caret position. + * + * @param {Object} [position] + * @return {this} + * @function + * @since 1.4.5 + * @name sourceEditorCaret + * @memberOf SCEditor.prototype + */ + base.sourceEditorCaret = function (position) { + sourceEditor.focus(); + + if (position) { + sourceEditor.selectionStart = position.start; + sourceEditor.selectionEnd = position.end; + + return this; + } + + return { + start: sourceEditor.selectionStart, + end: sourceEditor.selectionEnd + }; + }; + + /** + * Gets the value of the editor. + * + * If the editor is in WYSIWYG mode it will return the filtered + * HTML from it (converted to BBCode if using the BBCode plugin). + * It it's in Source Mode it will return the unfiltered contents + * of the source editor (if using the BBCode plugin this will be + * BBCode again). + * + * @since 1.3.5 + * @return {string} + * @function + * @name val + * @memberOf SCEditor.prototype + */ + /** + * Sets the value of the editor. + * + * If filter set true the val will be passed through the filter + * function. If using the BBCode plugin it will pass the val to + * the BBCode filter to convert any BBCode into HTML. + * + * @param {string} val + * @param {boolean} [filter=true] + * @return {this} + * @since 1.3.5 + * @function + * @name val^2 + * @memberOf SCEditor.prototype + */ + base.val = function (val, filter) { + if (!isString(val)) { + return base.inSourceMode() ? + base.getSourceEditorValue(false) : + base.getWysiwygEditorValue(filter); + } + + if (!base.inSourceMode()) { + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + base.setWysiwygEditorValue(val); + } else { + base.setSourceEditorValue(val); + } + + return base; + }; + + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @return {this} + * @since 1.3.5 + * @function + * @name insert + * @memberOf SCEditor.prototype + */ + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * If the allowMixed param is set to true, HTML any will not be + * escaped + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @param {boolean} [allowMixed=false] + * @return {this} + * @since 1.4.3 + * @function + * @name insert^2 + * @memberOf SCEditor.prototype + */ + // eslint-disable-next-line max-params + base.insert = function ( + start, end, filter, convertEmoticons, allowMixed + ) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(start, end); + return base; + } + + // Add the selection between start and end + if (end) { + var html = rangeHelper.selectedHtml(); + + if (filter !== false && 'fragmentToSource' in format) { + html = format + .fragmentToSource(html, wysiwygDocument, currentNode); + } + + start += html + end; + } + // TODO: This filter should allow empty tags as it's inserting. + if (filter !== false && 'fragmentToHtml' in format) { + start = format.fragmentToHtml(start, currentNode); + } + + // Convert any escaped HTML back into HTML if mixed is allowed + if (filter !== false && allowMixed === true) { + start = start.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + } + + base.wysiwygEditorInsertHtml(start); + + return base; + }; + + /** + * Gets the WYSIWYG editors HTML value. + * + * If using a plugin that filters the Ht Ml like the BBCode plugin + * it will return the result of the filtering (BBCode) unless the + * filter param is set to false. + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @name getWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.getWysiwygEditorValue = function (filter) { + var html; + // Create a tmp node to store contents so it can be modified + // without affecting anything else. + var tmp = createElement('div', {}, wysiwygDocument); + var childNodes = wysiwygBody.childNodes; + + for (var i = 0; i < childNodes.length; i++) { + appendChild(tmp, childNodes[i].cloneNode(true)); + } + + appendChild(wysiwygBody, tmp); + fixNesting(tmp); + remove(tmp); + + html = tmp.innerHTML; + + // filter the HTML and DOM through any plugins + if (filter !== false && format.hasOwnProperty('toSource')) { + html = format.toSource(html, wysiwygDocument); + } + + return html; + }; + + /** + * Gets the WYSIWYG editor's iFrame Body. + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getBody + * @memberOf SCEditor.prototype + */ + base.getBody = function () { + return wysiwygBody; + }; + + /** + * Gets the WYSIWYG editors container area (whole iFrame). + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getContentAreaContainer + * @memberOf SCEditor.prototype + */ + base.getContentAreaContainer = function () { + return wysiwygEditor; + }; + + /** + * Gets the text editor value + * + * If using a plugin that filters the text like the BBCode plugin + * it will return the result of the filtering which is BBCode to + * HTML so it will return HTML. If filter is set to false it will + * just return the contents of the source editor (BBCode). + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @since 1.4.0 + * @name getSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.getSourceEditorValue = function (filter) { + var val = sourceEditor.value; + + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + return val; + }; + + /** + * Sets the WYSIWYG HTML editor value. Should only be the HTML + * contained within the body tags + * + * @param {string} value + * @function + * @name setWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.setWysiwygEditorValue = function (value) { + if (!value) { + value = '

    ' + (IE_VER ? '' : '
    ') + '

    '; + } + + wysiwygBody.innerHTML = value; + replaceEmoticons(); + + appendNewLine(); + triggerValueChanged(); + autoExpand(); + }; + + /** + * Sets the text editor value + * + * @param {string} value + * @function + * @name setSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.setSourceEditorValue = function (value) { + sourceEditor.value = value; + + triggerValueChanged(); + }; + + /** + * Updates the textarea that the editor is replacing + * with the value currently inside the editor. + * + * @function + * @name updateOriginal + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.updateOriginal = function () { + original.value = base.val(); + }; + + /** + * Replaces any emoticon codes in the passed HTML + * with their emoticon images + * @private + */ + replaceEmoticons = function () { + if (options.emoticonsEnabled) { + replace(wysiwygBody, allEmoticons, options.emoticonsCompat); + } + }; + + /** + * If the editor is in source code mode + * + * @return {boolean} + * @function + * @name inSourceMode + * @memberOf SCEditor.prototype + */ + base.inSourceMode = function () { + return hasClass(editorContainer, 'sourceMode'); + }; + + /** + * Gets if the editor is in sourceMode + * + * @return boolean + * @function + * @name sourceMode + * @memberOf SCEditor.prototype + */ + /** + * Sets if the editor is in sourceMode + * + * @param {boolean} enable + * @return {this} + * @function + * @name sourceMode^2 + * @memberOf SCEditor.prototype + */ + base.sourceMode = function (enable) { + var inSourceMode = base.inSourceMode(); + + if (typeof enable !== 'boolean') { + return inSourceMode; + } + + if ((inSourceMode && !enable) || (!inSourceMode && enable)) { + base.toggleSourceMode(); + } + + return base; + }; + + /** + * Switches between the WYSIWYG and source modes + * + * @function + * @name toggleSourceMode + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.toggleSourceMode = function () { + var isInSourceMode = base.inSourceMode(); + + // don't allow switching to WYSIWYG if doesn't support it + if (!isWysiwygSupported && isInSourceMode) { + return; + } + + if (!isInSourceMode) { + rangeHelper.saveRange(); + rangeHelper.clear(); + } + + base.blur(); + + if (isInSourceMode) { + base.setWysiwygEditorValue(base.getSourceEditorValue()); + } else { + base.setSourceEditorValue(base.getWysiwygEditorValue()); + } + + lastRange = null; + toggle(sourceEditor); + toggle(wysiwygEditor); + + toggleClass(editorContainer, 'wysiwygMode', isInSourceMode); + toggleClass(editorContainer, 'sourceMode', !isInSourceMode); + + updateToolBar(); + updateActiveButtons(); + }; + + /** + * Gets the selected text of the source editor + * @return {string} + * @private + */ + sourceEditorSelectedText = function () { + sourceEditor.focus(); + + return sourceEditor.value.substring( + sourceEditor.selectionStart, + sourceEditor.selectionEnd + ); + }; + + /** + * Handles the passed command + * @private + */ + handleCommand = function (caller, cmd) { + // check if in text mode and handle text commands + if (base.inSourceMode()) { + if (cmd.txtExec) { + if (Array.isArray(cmd.txtExec)) { + base.sourceEditorInsertText.apply(base, cmd.txtExec); + } else { + cmd.txtExec.call(base, caller, sourceEditorSelectedText()); + } + } + } else if (cmd.exec) { + if (isFunction(cmd.exec)) { + cmd.exec.call(base, caller); + } else { + base.execCommand( + cmd.exec, + cmd.hasOwnProperty('execParam') ? cmd.execParam : null + ); + } + } + + }; + + /** + * Saves the current range. Needed for IE because it forgets + * where the cursor was and what was selected + * @private + */ + saveRange = function () { + /* this is only needed for IE */ + if (IE_VER) { + lastRange = rangeHelper.selectedRange(); + } + }; + + /** + * Executes a command on the WYSIWYG editor + * + * @param {string} command + * @param {String|Boolean} [param] + * @function + * @name execCommand + * @memberOf SCEditor.prototype + */ + base.execCommand = function (command, param) { + var executed = false, + commandObj = base.commands[command]; + + base.focus(); + + // TODO: make configurable + // don't apply any commands to code elements + if (closest(rangeHelper.parentNode(), 'code')) { + return; + } + + try { + executed = wysiwygDocument.execCommand(command, false, param); + } catch (ex) { } + + // show error if execution failed and an error message exists + if (!executed && commandObj && commandObj.errorMessage) { + /*global alert:false*/ + alert(base._(commandObj.errorMessage)); + } + + updateActiveButtons(); + }; + + /** + * Checks if the current selection has changed and triggers + * the selectionchanged event if it has. + * + * In browsers other than IE, it will check at most once every 100ms. + * This is because only IE has a selection changed event. + * @private + */ + checkSelectionChanged = function () { + function check() { + // Don't create new selection if there isn't one (like after + // blur event in iOS) + if (wysiwygWindow.getSelection() && + wysiwygWindow.getSelection().rangeCount <= 0) { + currentSelection = null; + // rangeHelper could be null if editor was destroyed + // before the timeout had finished + } else if (rangeHelper && !rangeHelper.compare(currentSelection)) { + currentSelection = rangeHelper.cloneSelected(); + + // If the selection is in an inline wrap it in a block. + // Fixes #331 + if (currentSelection && currentSelection.collapsed) { + var parent$$1 = currentSelection.startContainer; + var offset = currentSelection.startOffset; + + // Handle if selection is placed before/after an element + if (offset && parent$$1.nodeType !== TEXT_NODE) { + parent$$1 = parent$$1.childNodes[offset]; + } + + while (parent$$1 && parent$$1.parentNode !== wysiwygBody) { + parent$$1 = parent$$1.parentNode; + } + + if (parent$$1 && isInline(parent$$1, true)) { + rangeHelper.saveRange(); + wrapInlines(wysiwygBody, wysiwygDocument); + rangeHelper.restoreRange(); + } + } + + trigger(editorContainer, 'selectionchanged'); + } + + isSelectionCheckPending = false; + } + + if (isSelectionCheckPending) { + return; + } + + isSelectionCheckPending = true; + + // Don't need to limit checking if browser supports the Selection API + if ('onselectionchange' in wysiwygDocument) { + check(); + } else { + setTimeout(check, 100); + } + }; + + /** + * Checks if the current node has changed and triggers + * the nodechanged event if it has + * @private + */ + checkNodeChanged = function () { + // check if node has changed + var oldNode, + node = rangeHelper.parentNode(); + + if (currentNode !== node) { + oldNode = currentNode; + currentNode = node; + currentBlockNode = rangeHelper.getFirstBlockParent(node); + + trigger(editorContainer, 'nodechanged', { + oldNode: oldNode, + newNode: currentNode + }); + } + }; + + /** + * Gets the current node that contains the selection/caret in + * WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentNode + * @memberOf SCEditor.prototype + */ + base.currentNode = function () { + return currentNode; + }; + + /** + * Gets the first block level node that contains the + * selection/caret in WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentBlockNode + * @memberOf SCEditor.prototype + * @since 1.4.4 + */ + base.currentBlockNode = function () { + return currentBlockNode; + }; + + /** + * Updates if buttons are active or not + * @private + */ + updateActiveButtons = function () { + var firstBlock, parent$$1; + var activeClass = 'active'; + var doc = wysiwygDocument; + var isSource = base.sourceMode(); + + if (base.readOnly()) { + each(find(toolbar, activeClass), function (_, menuItem) { + removeClass(menuItem, activeClass); + }); + return; + } + + if (!isSource) { + parent$$1 = rangeHelper.parentNode(); + firstBlock = rangeHelper.getFirstBlockParent(parent$$1); + } + + for (var j = 0; j < btnStateHandlers.length; j++) { + var state = 0; + var btn = toolbarButtons[btnStateHandlers[j].name]; + var stateFn = btnStateHandlers[j].state; + var isDisabled = (isSource && !btn._sceTxtMode) || + (!isSource && !btn._sceWysiwygMode); + + if (isString(stateFn)) { + if (!isSource) { + try { + state = doc.queryCommandEnabled(stateFn) ? 0 : -1; + + // eslint-disable-next-line max-depth + if (state > -1) { + state = doc.queryCommandState(stateFn) ? 1 : 0; + } + } catch (ex) {} + } + } else if (!isDisabled) { + state = stateFn.call(base, parent$$1, firstBlock); + } + + toggleClass(btn, 'disabled', isDisabled || state < 0); + toggleClass(btn, activeClass, state > 0); + } + + if (icons && icons.update) { + icons.update(isSource, parent$$1, firstBlock); + } + }; + + /** + * Handles any key press in the WYSIWYG editor + * + * @private + */ + handleKeyPress = function (e) { + // FF bug: https://bugzilla.mozilla.org/show_bug.cgi?id=501496 + if (e.defaultPrevented) { + return; + } + + base.closeDropDown(); + + // 13 = enter key + if (e.which === 13) { + var LIST_TAGS = 'li,ul,ol'; + + // "Fix" (cludge) for blocklevel elements being duplicated in some + // browsers when enter is pressed instead of inserting a newline + if (!is(currentBlockNode, LIST_TAGS) && + hasStyling(currentBlockNode)) { + lastRange = null; + + var br = createElement('br', {}, wysiwygDocument); + rangeHelper.insertNode(br); + + // Last
    of a block will be collapsed unless it is + // IE < 11 so need to make sure the
    that was inserted + // isn't the last node of a block. + if (!IE_BR_FIX$2) { + var parent$$1 = br.parentNode; + var lastChild = parent$$1.lastChild; + + // Sometimes an empty next node is created after the
    + if (lastChild && lastChild.nodeType === TEXT_NODE && + lastChild.nodeValue === '') { + remove(lastChild); + lastChild = parent$$1.lastChild; + } + + // If this is the last BR of a block and the previous + // sibling is inline then will need an extra BR. This + // is needed because the last BR of a block will be + // collapsed. Fixes issue #248 + if (!isInline(parent$$1, true) && lastChild === br && + isInline(br.previousSibling)) { + rangeHelper.insertHTML('
    '); + } + } + + e.preventDefault(); + } + } + }; + + /** + * Makes sure that if there is a code or quote tag at the + * end of the editor, that there is a new line after it. + * + * If there wasn't a new line at the end you wouldn't be able + * to enter any text after a code/quote tag + * @return {void} + * @private + */ + appendNewLine = function () { + // Check all nodes in reverse until either add a new line + // or reach a non-empty textnode or BR at which point can + // stop checking. + rTraverse(wysiwygBody, function (node) { + // Last block, add new line after if has styling + if (node.nodeType === ELEMENT_NODE && + !/inline/.test(css(node, 'display'))) { + + // Add line break after if has styling + if (!is(node, '.sceditor-nlf') && hasStyling(node)) { + var paragraph = createElement('p', {}, wysiwygDocument); + paragraph.className = 'sceditor-nlf'; + paragraph.innerHTML = !IE_BR_FIX$2 ? '
    ' : ''; + appendChild(wysiwygBody, paragraph); + return false; + } + } + + // Last non-empty text node or line break. + // No need to add line-break after them + if ((node.nodeType === 3 && !/^\s*$/.test(node.nodeValue)) || + is(node, 'br')) { + return false; + } + }); + }; + + /** + * Handles form reset event + * @private + */ + handleFormReset = function () { + base.val(original.value); + }; + + /** + * Handles any mousedown press in the WYSIWYG editor + * @private + */ + handleMouseDown = function () { + base.closeDropDown(); + lastRange = null; + }; + + /** + * Translates the string into the locale language. + * + * Replaces any {0}, {1}, {2}, ect. with the params provided. + * + * @param {string} str + * @param {...String} args + * @return {string} + * @function + * @name _ + * @memberOf SCEditor.prototype + */ + base._ = function () { + var undef, + args = arguments; + + if (locale && locale[args[0]]) { + args[0] = locale[args[0]]; + } + + return args[0].replace(/\{(\d+)\}/g, function (str, p1) { + return args[p1 - 0 + 1] !== undef ? + args[p1 - 0 + 1] : + '{' + p1 + '}'; + }); + }; + + /** + * Passes events on to any handlers + * @private + * @return void + */ + handleEvent = function (e) { + if (pluginManager) { + // Send event to all plugins + pluginManager.call(e.type + 'Event', e, base); + } + + // convert the event into a custom event to send + var name = (e.target === sourceEditor ? 'scesrc' : 'scewys') + e.type; + + if (eventHandlers[name]) { + eventHandlers[name].forEach(function (fn) { + fn.call(base, e); + }); + } + }; + + /** + * Binds a handler to the specified events + * + * This function only binds to a limited list of + * supported events. + * + * The supported events are: + * + * * keyup + * * keydown + * * Keypress + * * blur + * * focus + * * nodechanged - When the current node containing + * the selection changes in WYSIWYG mode + * * contextmenu + * * selectionchanged + * * valuechanged + * + * + * The events param should be a string containing the event(s) + * to bind this handler to. If multiple, they should be separated + * by spaces. + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name bind + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.bind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + var wysEvent = 'scewys' + events[i]; + var srcEvent = 'scesrc' + events[i]; + // Use custom events to allow passing the instance as the + // 2nd argument. + // Also allows unbinding without unbinding the editors own + // event handlers. + if (!excludeWysiwyg) { + eventHandlers[wysEvent] = eventHandlers[wysEvent] || []; + eventHandlers[wysEvent].push(handler); + } + + if (!excludeSource) { + eventHandlers[srcEvent] = eventHandlers[srcEvent] || []; + eventHandlers[srcEvent].push(handler); + } + + // Start sending value changed events + if (events[i] === 'valuechanged') { + triggerValueChanged.hasHandler = true; + } + } + } + + return base; + }; + + /** + * Unbinds an event that was bound using bind(). + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude unbinding this + * handler from the WYSIWYG editor + * @param {boolean} excludeSource if to exclude unbinding this + * handler from the source editor + * @return {this} + * @function + * @name unbind + * @memberOf SCEditor.prototype + * @since 1.4.1 + * @see bind + */ + base.unbind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + if (!excludeWysiwyg) { + arrayRemove( + eventHandlers['scewys' + events[i]] || [], handler); + } + + if (!excludeSource) { + arrayRemove( + eventHandlers['scesrc' + events[i]] || [], handler); + } + } + } + + return base; + }; + + /** + * Blurs the editors input area + * + * @return {this} + * @function + * @name blur + * @memberOf SCEditor.prototype + * @since 1.3.6 + */ + /** + * Adds a handler to the editors blur event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name blur^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.blur = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('blur', handler, excludeWysiwyg, excludeSource); + } else if (!base.sourceMode()) { + wysiwygBody.blur(); + } else { + sourceEditor.blur(); + } + + return base; + }; + + /** + * Focuses the editors input area + * + * @return {this} + * @function + * @name focus + * @memberOf SCEditor.prototype + */ + /** + * Adds an event handler to the focus event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name focus^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.focus = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('focus', handler, excludeWysiwyg, excludeSource); + } else if (!base.inSourceMode()) { + // Already has focus so do nothing + if (find(wysiwygDocument, ':focus').length) { + return; + } + + var container; + var rng = rangeHelper.selectedRange(); + + // Fix FF bug where it shows the cursor in the wrong place + // if the editor hasn't had focus before. See issue #393 + if (!currentSelection) { + autofocus(); + } + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$2 && rng && rng.endOffset === 1 && rng.collapsed) { + container = rng.endContainer; + + if (container && container.childNodes.length === 1 && + is(container.firstChild, 'br')) { + rng.setStartBefore(container.firstChild); + rng.collapse(true); + rangeHelper.selectRange(rng); + } + } + + wysiwygWindow.focus(); + wysiwygBody.focus(); + + // Needed for IE + if (lastRange) { + rangeHelper.selectRange(lastRange); + + // Remove the stored range after being set. + // If the editor loses focus it should be saved again. + lastRange = null; + } + } else { + sourceEditor.focus(); + } + + updateActiveButtons(); + + return base; + }; + + /** + * Adds a handler to the key down event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyDown + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyDown = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keydown', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key press event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyPress + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyPress = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('keypress', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key up event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyUp + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyUp = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keyup', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the node changed event. + * + * Happens whenever the node containing the selection/caret + * changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name nodeChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.nodeChanged = function (handler) { + return base.bind('nodechanged', handler, false, true); + }; + + /** + * Adds a handler to the selection changed event + * + * Happens whenever the selection changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name selectionChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.selectionChanged = function (handler) { + return base.bind('selectionchanged', handler, false, true); + }; + + /** + * Adds a handler to the value changed event + * + * Happens whenever the current editor value changes. + * + * Whenever anything is inserted, the value changed or + * 1.5 secs after text is typed. If a space is typed it will + * cause the event to be triggered immediately instead of + * after 1.5 seconds + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name valueChanged + * @memberOf SCEditor.prototype + * @since 1.4.5 + */ + base.valueChanged = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('valuechanged', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Emoticons keypress handler + * @private + */ + emoticonsKeyPress = function (e) { + var replacedEmoticon, + cachePos = 0, + emoticonsCache = base.emoticonsCache, + curChar = String.fromCharCode(e.which); + + // TODO: Make configurable + if (closest(currentBlockNode, 'code')) { + return; + } + + if (!emoticonsCache) { + emoticonsCache = []; + + each(allEmoticons, function (key, html) { + emoticonsCache[cachePos++] = [key, html]; + }); + + emoticonsCache.sort(function (a, b) { + return a[0].length - b[0].length; + }); + + base.emoticonsCache = emoticonsCache; + base.longestEmoticonCode = + emoticonsCache[emoticonsCache.length - 1][0].length; + } + + replacedEmoticon = rangeHelper.replaceKeyword( + base.emoticonsCache, + true, + true, + base.longestEmoticonCode, + options.emoticonsCompat, + curChar + ); + + if (replacedEmoticon) { + if (!options.emoticonsCompat || !/^\s$/.test(curChar)) { + e.preventDefault(); + } + } + }; + + /** + * Makes sure emoticons are surrounded by whitespace + * @private + */ + emoticonsCheckWhitespace = function () { + checkWhitespace(currentBlockNode, rangeHelper); + }; + + /** + * Gets if emoticons are currently enabled + * @return {boolean} + * @function + * @name emoticons + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + /** + * Enables/disables emoticons + * + * @param {boolean} enable + * @return {this} + * @function + * @name emoticons^2 + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + base.emoticons = function (enable) { + if (!enable && enable !== false) { + return options.emoticonsEnabled; + } + + options.emoticonsEnabled = enable; + + if (enable) { + on(wysiwygBody, 'keypress', emoticonsKeyPress); + + if (!base.sourceMode()) { + rangeHelper.saveRange(); + + replaceEmoticons(); + triggerValueChanged(false); + + rangeHelper.restoreRange(); + } + } else { + var emoticons = + find(wysiwygBody, 'img[data-sceditor-emoticon]'); + + each(emoticons, function (_, img) { + var text = data(img, 'sceditor-emoticon'); + var textNode = wysiwygDocument.createTextNode(text); + img.parentNode.replaceChild(textNode, img); + }); + + off(wysiwygBody, 'keypress', emoticonsKeyPress); + + triggerValueChanged(); + } + + return base; + }; + + /** + * Gets the current WYSIWYG editors inline CSS + * + * @return {string} + * @function + * @name css + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + /** + * Sets inline CSS for the WYSIWYG editor + * + * @param {string} css + * @return {this} + * @function + * @name css^2 + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + base.css = function (css$$1) { + if (!inlineCss) { + inlineCss = createElement('style', { + id: 'inline' + }, wysiwygDocument); + + appendChild(wysiwygDocument.head, inlineCss); + } + + if (!isString(css$$1)) { + return inlineCss.styleSheet ? + inlineCss.styleSheet.cssText : inlineCss.innerHTML; + } + + if (inlineCss.styleSheet) { + inlineCss.styleSheet.cssText = css$$1; + } else { + inlineCss.innerHTML = css$$1; + } + + return base; + }; + + /** + * Handles the keydown event, used for shortcuts + * @private + */ + handleKeyDown = function (e) { + var shortcut = [], + SHIFT_KEYS = { + '`': '~', + '1': '!', + '2': '@', + '3': '#', + '4': '$', + '5': '%', + '6': '^', + '7': '&', + '8': '*', + '9': '(', + '0': ')', + '-': '_', + '=': '+', + ';': ': ', + '\'': '"', + ',': '<', + '.': '>', + '/': '?', + '\\': '|', + '[': '{', + ']': '}' + }, + SPECIAL_KEYS = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 19: 'pause', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'insert', + 46: 'del', + 91: 'win', + 92: 'win', + 93: 'select', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9', + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111: '/', + 112: 'f1', + 113: 'f2', + 114: 'f3', + 115: 'f4', + 116: 'f5', + 117: 'f6', + 118: 'f7', + 119: 'f8', + 120: 'f9', + 121: 'f10', + 122: 'f11', + 123: 'f12', + 144: 'numlock', + 145: 'scrolllock', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }, + NUMPAD_SHIFT_KEYS = { + 109: '-', + 110: 'del', + 111: '/', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }, + which = e.which, + character = SPECIAL_KEYS[which] || + String.fromCharCode(which).toLowerCase(); + + if (e.ctrlKey || e.metaKey) { + shortcut.push('ctrl'); + } + + if (e.altKey) { + shortcut.push('alt'); + } + + if (e.shiftKey) { + shortcut.push('shift'); + + if (NUMPAD_SHIFT_KEYS[which]) { + character = NUMPAD_SHIFT_KEYS[which]; + } else if (SHIFT_KEYS[character]) { + character = SHIFT_KEYS[character]; + } + } + + // Shift is 16, ctrl is 17 and alt is 18 + if (character && (which < 16 || which > 18)) { + shortcut.push(character); + } + + shortcut = shortcut.join('+'); + if (shortcutHandlers[shortcut] && + shortcutHandlers[shortcut].call(base) === false) { + + e.stopPropagation(); + e.preventDefault(); + } + }; + + /** + * Adds a shortcut handler to the editor + * @param {string} shortcut + * @param {String|Function} cmd + * @return {sceditor} + */ + base.addShortcut = function (shortcut, cmd) { + shortcut = shortcut.toLowerCase(); + + if (isString(cmd)) { + shortcutHandlers[shortcut] = function () { + handleCommand(toolbarButtons[cmd], base.commands[cmd]); + + return false; + }; + } else { + shortcutHandlers[shortcut] = cmd; + } + + return base; + }; + + /** + * Removes a shortcut handler + * @param {string} shortcut + * @return {sceditor} + */ + base.removeShortcut = function (shortcut) { + delete shortcutHandlers[shortcut.toLowerCase()]; + + return base; + }; + + /** + * Handles the backspace key press + * + * Will remove block styling like quotes/code ect if at the start. + * @private + */ + handleBackSpace = function (e) { + var node, offset, range, parent$$1; + + // 8 is the backspace key + if (options.disableBlockRemove || e.which !== 8 || + !(range = rangeHelper.selectedRange())) { + return; + } + + node = range.startContainer; + offset = range.startOffset; + + if (offset !== 0 || !(parent$$1 = currentStyledBlockNode()) || + is(parent$$1, 'body')) { + return; + } + + while (node !== parent$$1) { + while (node.previousSibling) { + node = node.previousSibling; + + // Everything but empty text nodes before the cursor + // should prevent the style from being removed + if (node.nodeType !== TEXT_NODE || node.nodeValue) { + return; + } + } + + if (!(node = node.parentNode)) { + return; + } + } + + // The backspace was pressed at the start of + // the container so clear the style + base.clearBlockFormatting(parent$$1); + e.preventDefault(); + }; + + /** + * Gets the first styled block node that contains the cursor + * @return {HTMLElement} + */ + currentStyledBlockNode = function () { + var block = currentBlockNode; + + while (!hasStyling(block) || isInline(block, true)) { + if (!(block = block.parentNode) || is(block, 'body')) { + return; + } + } + + return block; + }; + + /** + * Clears the formatting of the passed block element. + * + * If block is false, if will clear the styling of the first + * block level element that contains the cursor. + * @param {HTMLElement} block + * @since 1.4.4 + */ + base.clearBlockFormatting = function (block) { + block = block || currentStyledBlockNode(); + + if (!block || is(block, 'body')) { + return base; + } + + rangeHelper.saveRange(); + + block.className = ''; + lastRange = null; + + attr(block, 'style', ''); + + if (!is(block, 'p,div,td')) { + convertElement(block, 'p'); + } + + rangeHelper.restoreRange(); + return base; + }; + + /** + * Triggers the valueChanged signal if there is + * a plugin that handles it. + * + * If rangeHelper.saveRange() has already been + * called, then saveRange should be set to false + * to prevent the range being saved twice. + * + * @since 1.4.5 + * @param {boolean} saveRange If to call rangeHelper.saveRange(). + * @private + */ + triggerValueChanged = function (saveRange) { + if (!pluginManager || + (!pluginManager.hasHandler('valuechangedEvent') && + !triggerValueChanged.hasHandler)) { + return; + } + + var currentHtml, + sourceMode = base.sourceMode(), + hasSelection = !sourceMode && rangeHelper.hasSelection(); + + // Composition end isn't guaranteed to fire but must have + // ended when triggerValueChanged() is called so reset it + isComposing = false; + + // Don't need to save the range if sceditor-start-marker + // is present as the range is already saved + saveRange = saveRange !== false && + !wysiwygDocument.getElementById('sceditor-start-marker'); + + // Clear any current timeout as it's now been triggered + if (valueChangedKeyUpTimer) { + clearTimeout(valueChangedKeyUpTimer); + valueChangedKeyUpTimer = false; + } + + if (hasSelection && saveRange) { + rangeHelper.saveRange(); + } + + currentHtml = sourceMode ? sourceEditor.value : wysiwygBody.innerHTML; + + // Only trigger if something has actually changed. + if (currentHtml !== triggerValueChanged.lastVal) { + triggerValueChanged.lastVal = currentHtml; + + trigger(editorContainer, 'valuechanged', { + rawValue: sourceMode ? base.val() : currentHtml + }); + } + + if (hasSelection && saveRange) { + rangeHelper.removeMarkers(); + } + }; + + /** + * Should be called whenever there is a blur event + * @private + */ + valueChangedBlur = function () { + if (valueChangedKeyUpTimer) { + triggerValueChanged(); + } + }; + + /** + * Should be called whenever there is a keypress event + * @param {Event} e The keypress event + * @private + */ + valueChangedKeyUp = function (e) { + var which = e.which, + lastChar = valueChangedKeyUp.lastChar, + lastWasSpace = (lastChar === 13 || lastChar === 32), + lastWasDelete = (lastChar === 8 || lastChar === 46); + + valueChangedKeyUp.lastChar = which; + + if (isComposing) { + return; + } + + // 13 = return & 32 = space + if (which === 13 || which === 32) { + if (!lastWasSpace) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + // 8 = backspace & 46 = del + } else if (which === 8 || which === 46) { + if (!lastWasDelete) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + } else if (valueChangedKeyUp.triggerNext) { + triggerValueChanged(); + valueChangedKeyUp.triggerNext = false; + } + + // Clear the previous timeout and set a new one. + clearTimeout(valueChangedKeyUpTimer); + + // Trigger the event 1.5s after the last keypress if space + // isn't pressed. This might need to be lowered, will need + // to look into what the slowest average Chars Per Min is. + valueChangedKeyUpTimer = setTimeout(function () { + if (!isComposing) { + triggerValueChanged(); + } + }, 1500); + }; + + handleComposition = function (e) { + isComposing = /start/i.test(e.type); + + if (!isComposing) { + triggerValueChanged(); + } + }; + + autoUpdate = function () { + base.updateOriginal(); + }; + + // run the initializer + init(); + } + + + /** + * Map containing the loaded SCEditor locales + * @type {Object} + * @name locale + * @memberOf sceditor + */ + SCEditor.locale = {}; + + SCEditor.formats = {}; + SCEditor.icons = {}; + + + /** + * Static command helper class + * @class command + * @name sceditor.command + */ + SCEditor.command = + /** @lends sceditor.command */ + { + /** + * Gets a command + * + * @param {string} name + * @return {Object|null} + * @since v1.3.5 + */ + get: function (name) { + return defaultCmds[name] || null; + }, + + /** + *

    Adds a command to the editor or updates an existing + * command if a command with the specified name already exists.

    + * + *

    Once a command is add it can be included in the toolbar by + * adding it's name to the toolbar option in the constructor. It + * can also be executed manually by calling + * {@link sceditor.execCommand}

    + * + * @example + * SCEditor.command.set("hello", + * { + * exec: function () { + * alert("Hello World!"); + * } + * }); + * + * @param {string} name + * @param {Object} cmd + * @return {this|false} Returns false if name or cmd is false + * @since v1.3.5 + */ + set: function (name, cmd) { + if (!name || !cmd) { + return false; + } + + // merge any existing command properties + cmd = extend(defaultCmds[name] || {}, cmd); + + cmd.remove = function () { + SCEditor.command.remove(name); + }; + + defaultCmds[name] = cmd; + return this; + }, + + /** + * Removes a command + * + * @param {string} name + * @return {this} + * @since v1.3.5 + */ + remove: function (name) { + if (defaultCmds[name]) { + delete defaultCmds[name]; + } + + return this; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + */ + + window.sceditor = { + command: SCEditor.command, + commands: defaultCmds, + defaultOptions: defaultOptions, + + ie: ie, + ios: ios, + isWysiwygSupported: isWysiwygSupported, + + regexEscape: regex, + escapeEntities: entities, + escapeUriScheme: uriScheme, + + dom: { + css: css, + attr: attr, + removeAttr: removeAttr, + is: is, + closest: closest, + width: width, + height: height, + traverse: traverse, + rTraverse: rTraverse, + parseHTML: parseHTML, + hasStyling: hasStyling, + convertElement: convertElement, + blockLevelList: blockLevelList, + canHaveChildren: canHaveChildren, + isInline: isInline, + copyCSS: copyCSS, + fixNesting: fixNesting, + findCommonAncestor: findCommonAncestor, + getSibling: getSibling, + removeWhiteSpace: removeWhiteSpace, + extractContents: extractContents, + getOffset: getOffset, + getStyle: getStyle, + hasStyle: hasStyle + }, + locale: SCEditor.locale, + icons: SCEditor.icons, + utils: { + each: each, + isEmptyObject: isEmptyObject, + extend: extend + }, + plugins: PluginManager.plugins, + formats: SCEditor.formats, + create: function (textarea, options) { + options = options || {}; + + // Don't allow the editor to be initialised + // on it's own source editor + if (parent(textarea, '.sceditor-container')) { + return; + } + + if (options.runWithoutWysiwygSupport || isWysiwygSupported) { + /*eslint no-new: off*/ + (new SCEditor(textarea, options)); + } + }, + instance: function (textarea) { + return textarea._sceditor; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + * @requires jQuery + */ + + // For backwards compatibility + $.sceditor = window.sceditor; + + /** + * Creates an instance of sceditor on all textareas + * matched by the jQuery selector. + * + * If options is set to "state" it will return bool value + * indicating if the editor has been initialised on the + * matched textarea(s). If there is only one textarea + * it will return the bool value for that textarea. + * If more than one textarea is matched it will + * return an array of bool values for each textarea. + * + * If options is set to "instance" it will return the + * current editor instance for the textarea(s). Like the + * state option, if only one textarea is matched this will + * return just the instance for that textarea. If more than + * one textarea is matched it will return an array of + * instances each textarea. + * + * @param {Object|string} [options] Should either be an Object of options or + * the strings "state" or "instance" + * @return {this|Array|Array|SCEditor|boolean} + */ + $.fn.sceditor = function (options) { + var instance; + var ret = []; + + this.each(function () { + instance = this._sceditor; + + // Add state of instance to ret if that is what options is set to + if (options === 'state') { + ret.push(!!instance); + } else if (options === 'instance') { + ret.push(instance); + } else if (!instance) { + $.sceditor.create(this, options); + } + }); + + // If nothing in the ret array then must be init so return this + if (!ret.length) { + return this; + } + + return ret.length === 1 ? ret[0] : ret; + }; + +}(jQuery)); +;/** + * SCEditor XHTML Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (sceditor) { + 'use strict'; + + var IE_VER = sceditor.ie; + + // In IE < 11 a BR at the end of a block level element + // causes a double line break. + var IE_BR_FIX = IE_VER && IE_VER < 11; + + var dom = sceditor.dom; + var utils = sceditor.utils; + + var css = dom.css; + var attr = dom.attr; + var is = dom.is; + var removeAttr = dom.removeAttr; + var convertElement = dom.convertElement; + var extend = utils.extend; + var each = utils.each; + var isEmptyObject = utils.isEmptyObject; + + var getEditorCommand = sceditor.command.get; + + var defaultCommandsOverrides = { + bold: { + txtExec: ['', ''] + }, + italic: { + txtExec: ['', ''] + }, + underline: { + txtExec: ['', ''] + }, + strike: { + txtExec: ['', ''] + }, + subscript: { + txtExec: ['', ''] + }, + superscript: { + txtExec: ['', ''] + }, + left: { + txtExec: ['
    ', '
    '] + }, + center: { + txtExec: ['
    ', '
    '] + }, + right: { + txtExec: ['
    ', '
    '] + }, + justify: { + txtExec: ['
    ', '
    '] + }, + font: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('font')._dropDown( + editor, + caller, + function (font) { + editor.insertText('', ''); + } + ); + } + }, + size: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('size')._dropDown( + editor, + caller, + function (size) { + editor.insertText('', ''); + } + ); + } + }, + color: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('color')._dropDown( + editor, + caller, + function (color) { + editor.insertText('', ''); + } + ); + } + }, + bulletlist: { + txtExec: ['
    • ', '
    '] + }, + orderedlist: { + txtExec: ['
    1. ', '
    '] + }, + table: { + txtExec: ['
    ', '
    '] + }, + horizontalrule: { + txtExec: ['
    '] + }, + code: { + txtExec: ['', ''] + }, + image: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('image')._dropDown( + editor, + caller, + selected, + function (url, width, height) { + var attrs = ''; + + if (width) { + attrs += ' width="' + width + '"'; + } + + if (height) { + attrs += ' height="' + height + '"'; + } + + editor.insertText( + '' + ); + } + ); + } + }, + email: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('email')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '' + + (text || selected || url) + + '' + ); + } + ); + } + }, + link: { + txtExec: function (caller, selected) { + var editor = this; + + getEditorCommand('link')._dropDown( + editor, + caller, + function (url, text) { + editor.insertText( + '' + + (text || selected || url) + + '' + ); + } + ); + } + }, + quote: { + txtExec: ['
    ', '
    '] + }, + youtube: { + txtExec: function (caller) { + var editor = this; + + getEditorCommand('youtube')._dropDown( + editor, + caller, + function (id, time) { + editor.insertText( + '' + ); + } + ); + } + }, + rtl: { + txtExec: ['
    ', '
    '] + }, + ltr: { + txtExec: ['
    ', '
    '] + } + }; + + /** + * XHTMLSerializer part of the XHTML plugin. + * + * @class XHTMLSerializer + * @name jQuery.sceditor.XHTMLSerializer + * @since v1.4.1 + */ + sceditor.XHTMLSerializer = function () { + var base = this; + + var opts = { + indentStr: '\t' + }; + + /** + * Array containing the output, used as it's faster + * than string concatenation in slow browsers. + * @type {Array} + * @private + */ + var outputStringBuilder = []; + + /** + * Current indention level + * @type {number} + * @private + */ + var currentIndent = 0; + + // TODO: use escape.entities + /** + * Escapes XHTML entities + * + * @param {string} str + * @return {string} + * @private + */ + function escapeEntities(str) { + var entities = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\xa0': ' ' + }; + + return !str ? '' : str.replace(/[&<>"\xa0]/g, function (entity) { + return entities[entity] || entity; + }); + }; + + /** + * @param {string} str + * @return {string} + * @private + */ + function trim(str) { + return str + // New lines will be shown as spaces so just convert to spaces. + .replace(/[\r\n]/, ' ') + .replace(/[^\S|\u00A0]+/g, ' '); + }; + + /** + * Serializes a node to XHTML + * + * @param {Node} node Node to serialize + * @param {boolean} onlyChildren If to only serialize the nodes + * children and not the node + * itself + * @return {string} The serialized node + * @name serialize + * @memberOf jQuery.sceditor.XHTMLSerializer.prototype + * @since v1.4.1 + */ + base.serialize = function (node, onlyChildren) { + outputStringBuilder = []; + + if (onlyChildren) { + node = node.firstChild; + + while (node) { + serializeNode(node); + node = node.nextSibling; + } + } else { + serializeNode(node); + } + + return outputStringBuilder.join(''); + }; + + /** + * Serializes a node to the outputStringBuilder + * + * @param {Node} node + * @return {void} + * @private + */ + function serializeNode(node, parentIsPre) { + switch (node.nodeType) { + case 1: // element + var tagName = node.nodeName.toLowerCase(); + + // IE comment + if (tagName === '!') { + handleComment(node); + } else { + handleElement(node, parentIsPre); + } + break; + + case 3: // text + handleText(node, parentIsPre); + break; + + case 4: // cdata section + handleCdata(node); + break; + + case 8: // comment + handleComment(node); + break; + + case 9: // document + case 11: // document fragment + handleDoc(node); + break; + + // Ignored types + case 2: // attribute + case 5: // entity ref + case 6: // entity + case 7: // processing instruction + case 10: // document type + case 12: // notation + break; + } + }; + + /** + * Handles doc node + * @param {Node} node + * @return {void} + * @private + */ + function handleDoc(node) { + var child = node.firstChild; + + while (child) { + serializeNode(child); + child = child.nextSibling; + } + }; + + /** + * Handles element nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleElement(node, parentIsPre) { + var child, attr, attrValue, + tagName = node.nodeName.toLowerCase(), + isIframe = tagName === 'iframe', + attrIdx = node.attributes.length, + firstChild = node.firstChild, + // pre || pre-wrap with any vendor prefix + isPre = parentIsPre || + /pre(?:\-wrap)?$/i.test(css(node, 'whiteSpace')), + selfClosing = !node.firstChild && !dom.canHaveChildren(node) && + !isIframe; + + if (is(node, '.sceditor-ignore')) { + return; + } + + output('<' + tagName, !parentIsPre && canIndent(node)); + while (attrIdx--) { + attr = node.attributes[attrIdx]; + + attrValue = attr.value; + + output(' ' + attr.name.toLowerCase() + '="' + + escapeEntities(attrValue) + '"', false); + } + output(selfClosing ? ' />' : '>', false); + + if (!isIframe) { + child = firstChild; + } + + while (child) { + currentIndent++; + + serializeNode(child, isPre); + child = child.nextSibling; + + currentIndent--; + } + + if (!selfClosing) { + output( + '', + !isPre && !isIframe && canIndent(node) && + firstChild && canIndent(firstChild) + ); + } + }; + + /** + * Handles CDATA nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleCdata(node) { + output(''); + }; + + /** + * Handles comment nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleComment(node) { + output(''); + }; + + /** + * Handles text nodes + * @param {Node} node + * @return {void} + * @private + */ + function handleText(node, parentIsPre) { + var text = node.nodeValue; + + if (!parentIsPre) { + text = trim(text); + } + + if (text) { + output(escapeEntities(text), !parentIsPre && canIndent(node)); + } + }; + + /** + * Adds a string to the outputStringBuilder. + * + * The string will be indented unless indent is set to boolean false. + * @param {string} str + * @param {boolean} indent + * @return {void} + * @private + */ + function output(str, indent) { + var i = currentIndent; + + if (indent !== false) { + // Don't add a new line if it's the first element + if (outputStringBuilder.length) { + outputStringBuilder.push('\n'); + } + + while (i--) { + outputStringBuilder.push(opts.indentStr); + } + } + + outputStringBuilder.push(str); + }; + + /** + * Checks if should indent the node or not + * @param {Node} node + * @return {boolean} + * @private + */ + function canIndent(node) { + var prev = node.previousSibling; + + if (node.nodeType !== 1 && prev) { + return !dom.isInline(prev); + } + + // first child of a block element + if (!prev && !dom.isInline(node.parentNode)) { + return true; + } + + return !dom.isInline(node); + }; + }; + + /** + * SCEditor XHTML plugin + * @class xhtml + * @name jQuery.sceditor.plugins.xhtml + * @since v1.4.1 + */ + function xhtmlFormat() { + var base = this; + + /** + * Tag converters cache + * @type {Object} + * @private + */ + var tagConvertersCache = {}; + + /** + * Attributes filter cache + * @type {Object} + * @private + */ + var attrsCache = {}; + + /** + * Init + * @return {void} + */ + base.init = function () { + if (!isEmptyObject(xhtmlFormat.converters || {})) { + each( + xhtmlFormat.converters, + function (idx, converter) { + each(converter.tags, function (tagname) { + if (!tagConvertersCache[tagname]) { + tagConvertersCache[tagname] = []; + } + + tagConvertersCache[tagname].push(converter); + }); + } + ); + } + + this.commands = extend(true, + {}, defaultCommandsOverrides, this.commands); + }; + + /** + * Converts the WYSIWYG content to XHTML + * + * @param {boolean} isFragment + * @param {string} html + * @param {Document} context + * @param {HTMLElement} [parent] + * @return {string} + * @memberOf jQuery.sceditor.plugins.xhtml.prototype + */ + function toSource(isFragment, html, context) { + var xhtml, + container = context.createElement('div'); + container.innerHTML = html; + + css(container, 'visibility', 'hidden'); + context.body.appendChild(container); + + convertTags(container); + removeTags(container); + removeAttribs(container); + + if (!isFragment) { + wrapInlines(container); + } + + xhtml = (new sceditor.XHTMLSerializer()).serialize(container, true); + + context.body.removeChild(container); + + return xhtml; + }; + + base.toSource = toSource.bind(null, false); + + base.fragmentToSource = toSource.bind(null, true);; + + /** + * Runs all converters for the specified tagName + * against the DOM node. + * @param {string} tagName + * @return {Node} node + * @private + */ + function convertNode(tagName, node) { + if (!tagConvertersCache[tagName]) { + return; + } + + tagConvertersCache[tagName].forEach(function (converter) { + if (converter.tags[tagName]) { + each(converter.tags[tagName], function (attr, values) { + if (!node.getAttributeNode) { + return; + } + + attr = node.getAttributeNode(attr); + + if (!attr || values && values.indexOf(attr.value) < 0) { + return; + } + + converter.conv.call(base, node); + }); + } else if (converter.conv) { + converter.conv.call(base, node); + } + }); + }; + + /** + * Converts any tags/attributes to their XHTML equivalents + * @param {Node} node + * @return {void} + * @private + */ + function convertTags(node) { + dom.traverse(node, function (node) { + var tagName = node.nodeName.toLowerCase(); + + convertNode('*', node); + convertNode(tagName, node); + }, true); + }; + + /** + * Tests if a node is empty and can be removed. + * + * @param {Node} node + * @return {boolean} + * @private + */ + function isEmpty(node, excludeBr) { + var rect, + childNodes = node.childNodes, + tagName = node.nodeName.toLowerCase(), + nodeValue = node.nodeValue, + childrenLength = childNodes.length, + allowedEmpty = xhtmlFormat.allowedEmptyTags || []; + + if (excludeBr && tagName === 'br') { + return true; + } + + if (is(node, '.sceditor-ignore')) { + return true; + } + + if (allowedEmpty.indexOf(tagName) > -1 || tagName === 'td' || + !dom.canHaveChildren(node)) { + + return false; + } + + // \S|\u00A0 = any non space char + if (nodeValue && /\S|\u00A0/.test(nodeValue)) { + return false; + } + + while (childrenLength--) { + if (!isEmpty(childNodes[childrenLength], + excludeBr && !node.previousSibling && !node.nextSibling)) { + return false; + } + } + + // Treat tags with a width and height from CSS as not empty + if (node.getBoundingClientRect && + (node.className || node.hasAttributes('style'))) { + rect = node.getBoundingClientRect(); + return !rect.width || !rect.height; + } + + return true; + }; + + /** + * Removes any tags that are not white listed or if no + * tags are white listed it will remove any tags that + * are black listed. + * + * @param {Node} rootNode + * @return {void} + * @private + */ + function removeTags(rootNode) { + dom.traverse(rootNode, function (node) { + var remove, + tagName = node.nodeName.toLowerCase(), + parentNode = node.parentNode, + nodeType = node.nodeType, + isBlock = !dom.isInline(node), + previousSibling = node.previousSibling, + nextSibling = node.nextSibling, + isTopLevel = parentNode === rootNode, + noSiblings = !previousSibling && !nextSibling, + empty = tagName !== 'iframe' && isEmpty(node, + isTopLevel && noSiblings && tagName !== 'br'), + document = node.ownerDocument, + allowedTags = xhtmlFormat.allowedTags, + firstChild = node.firstChild, + disallowedTags = xhtmlFormat.disallowedTags; + + // 3 = text node + if (nodeType === 3) { + return; + } + + if (nodeType === 4) { + tagName = '!cdata'; + } else if (tagName === '!' || nodeType === 8) { + tagName = '!comment'; + } + + if (nodeType === 1) { + // skip empty nlf elements (new lines automatically + // added after block level elements like quotes) + if (is(node, '.sceditor-nlf')) { + if (!firstChild || (!IE_BR_FIX && + node.childNodes.length === 1 && + /br/i.test(firstChild.nodeName))) { + // Mark as empty,it will be removed by the next code + empty = true; + } else { + node.classList.remove('sceditor-nlf'); + + if (!node.className) { + removeAttr(node, 'class'); + } + } + } + } + + if (empty) { + remove = true; + // 3 is text node which do not get filtered + } else if (allowedTags && allowedTags.length) { + remove = (allowedTags.indexOf(tagName) < 0); + } else if (disallowedTags && disallowedTags.length) { + remove = (disallowedTags.indexOf(tagName) > -1); + } + + if (remove) { + if (!empty) { + if (isBlock && previousSibling && + dom.isInline(previousSibling)) { + parentNode.insertBefore( + document.createTextNode(' '), node); + } + + // Insert all the childen after node + while (node.firstChild) { + parentNode.insertBefore(node.firstChild, + nextSibling); + } + + if (isBlock && nextSibling && + dom.isInline(nextSibling)) { + parentNode.insertBefore( + document.createTextNode(' '), nextSibling); + } + } + + parentNode.removeChild(node); + } + }, true); + }; + + /** + * Merges two sets of attribute filters into one + * + * @param {Object} filtersA + * @param {Object} filtersB + * @return {Object} + * @private + */ + function mergeAttribsFilters(filtersA, filtersB) { + var ret = {}; + + if (filtersA) { + extend(ret, filtersA); + } + + if (!filtersB) { + return ret; + } + + each(filtersB, function (attrName, values) { + if (Array.isArray(values)) { + ret[attrName] = (ret[attrName] || []).concat(values); + } else if (!ret[attrName]) { + ret[attrName] = null; + } + }); + + return ret; + }; + + /** + * Wraps adjacent inline child nodes of root + * in paragraphs. + * + * @param {Node} root + * @private + */ + function wrapInlines(root) { + // Strip empty text nodes so they don't get wrapped. + dom.removeWhiteSpace(root); + + var wrapper; + var node = root.firstChild; + var next; + while (node) { + next = node.nextSibling; + + if (dom.isInline(node) && !is(node, '.sceditor-ignore')) { + if (!wrapper) { + wrapper = root.ownerDocument.createElement('p'); + node.parentNode.insertBefore(wrapper, node); + } + + wrapper.appendChild(node); + } else { + wrapper = null; + } + + node = next; + } + }; + + /** + * Removes any attributes that are not white listed or + * if no attributes are white listed it will remove + * any attributes that are black listed. + * @param {Node} node + * @return {void} + * @private + */ + function removeAttribs(node) { + var tagName, attr, attrName, attrsLength, validValues, remove, + allowedAttribs = xhtmlFormat.allowedAttribs, + isAllowed = allowedAttribs && + !isEmptyObject(allowedAttribs), + disallowedAttribs = xhtmlFormat.disallowedAttribs, + isDisallowed = disallowedAttribs && + !isEmptyObject(disallowedAttribs); + + attrsCache = {}; + + dom.traverse(node, function (node) { + if (!node.attributes) { + return; + } + + tagName = node.nodeName.toLowerCase(); + attrsLength = node.attributes.length; + + if (attrsLength) { + if (!attrsCache[tagName]) { + if (isAllowed) { + attrsCache[tagName] = mergeAttribsFilters( + allowedAttribs['*'], + allowedAttribs[tagName] + ); + } else { + attrsCache[tagName] = mergeAttribsFilters( + disallowedAttribs['*'], + disallowedAttribs[tagName] + ); + } + } + + while (attrsLength--) { + attr = node.attributes[attrsLength]; + attrName = attr.name; + validValues = attrsCache[tagName][attrName]; + remove = false; + + if (isAllowed) { + remove = validValues !== null && + (!Array.isArray(validValues) || + validValues.indexOf(attr.value) < 0); + } else if (isDisallowed) { + remove = validValues === null || + (Array.isArray(validValues) && + validValues.indexOf(attr.value) > -1); + } + + if (remove) { + node.removeAttribute(attrName); + } + } + } + }); + }; + }; + + /** + * Tag conveters, a converter is applied to all + * tags that match the criteria. + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.converters + * @since v1.4.1 + */ + xhtmlFormat.converters = [ + { + tags: { + '*': { + width: null + } + }, + conv: function (node) { + css(node, 'width', attr(node, 'width')); + removeAttr(node, 'width'); + } + }, + { + tags: { + '*': { + height: null + } + }, + conv: function (node) { + css(node, 'height', attr(node, 'height')); + removeAttr(node, 'height'); + } + }, + { + tags: { + 'li': { + value: null + } + }, + conv: function (node) { + removeAttr(node, 'value'); + } + }, + { + tags: { + '*': { + text: null + } + }, + conv: function (node) { + css(node, 'color', attr(node, 'text')); + removeAttr(node, 'text'); + } + }, + { + tags: { + '*': { + color: null + } + }, + conv: function (node) { + css(node, 'color', attr(node, 'color')); + removeAttr(node, 'color'); + } + }, + { + tags: { + '*': { + face: null + } + }, + conv: function (node) { + css(node, 'fontFamily', attr(node, 'face')); + removeAttr(node, 'face'); + } + }, + { + tags: { + '*': { + align: null + } + }, + conv: function (node) { + css(node, 'textAlign', attr(node, 'align')); + removeAttr(node, 'align'); + } + }, + { + tags: { + '*': { + border: null + } + }, + conv: function (node) { + css(node, 'borderWidth', attr(node, 'border')); + removeAttr(node, 'border'); + } + }, + { + tags: { + applet: { + name: null + }, + img: { + name: null + }, + layer: { + name: null + }, + map: { + name: null + }, + object: { + name: null + }, + param: { + name: null + } + }, + conv: function (node) { + if (!attr(node, 'id')) { + attr(node, 'id', attr(node, 'name')); + } + + removeAttr(node, 'name'); + } + }, + { + tags: { + '*': { + vspace: null + } + }, + conv: function (node) { + css(node, 'marginTop', attr(node, 'vspace') - 0); + css(node, 'marginBottom', attr(node, 'vspace') - 0); + removeAttr(node, 'vspace'); + } + }, + { + tags: { + '*': { + hspace: null + } + }, + conv: function (node) { + css(node, 'marginLeft', attr(node, 'hspace') - 0); + css(node, 'marginRight', attr(node, 'hspace') - 0); + removeAttr(node, 'hspace'); + } + }, + { + tags: { + 'hr': { + noshade: null + } + }, + conv: function (node) { + css(node, 'borderStyle', 'solid'); + removeAttr(node, 'noshade'); + } + }, + { + tags: { + '*': { + nowrap: null + } + }, + conv: function (node) { + css(node, 'whiteSpace', 'nowrap'); + removeAttr(node, 'nowrap'); + } + }, + { + tags: { + big: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'fontSize', 'larger'); + } + }, + { + tags: { + small: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'fontSize', 'smaller'); + } + }, + { + tags: { + b: null + }, + conv: function (node) { + convertElement(node, 'strong'); + } + }, + { + tags: { + u: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'textDecoration', + 'underline'); + } + }, + { + tags: { + s: null, + strike: null + }, + conv: function (node) { + css(convertElement(node, 'span'), 'textDecoration', + 'line-through'); + } + }, + { + tags: { + dir: null + }, + conv: function (node) { + convertElement(node, 'ul'); + } + }, + { + tags: { + center: null + }, + conv: function (node) { + css(convertElement(node, 'div'), 'textAlign', 'center'); + } + }, + { + tags: { + font: { + size: null + } + }, + conv: function (node) { + css(node, 'fontSize', css(node, 'fontSize')); + removeAttr(node, 'size'); + } + }, + { + tags: { + font: null + }, + conv: function (node) { + // All it's attributes will be converted + // by the attribute converters + convertElement(node, 'span'); + } + }, + { + tags: { + '*': { + type: ['_moz'] + } + }, + conv: function (node) { + removeAttr(node, 'type'); + } + }, + { + tags: { + '*': { + '_moz_dirty': null + } + }, + conv: function (node) { + removeAttr(node, '_moz_dirty'); + } + }, + { + tags: { + '*': { + '_moz_editor_bogus_node': null + } + }, + conv: function (node) { + node.parentNode.removeChild(node); + } + } + ]; + + /** + * Allowed attributes map. + * + * To allow an attribute for all tags use * as the tag name. + * + * Leave empty or null to allow all attributes. (the disallow + * list will be used to filter them instead) + * @type {Object} + * @name jQuery.sceditor.plugins.xhtml.allowedAttribs + * @since v1.4.1 + */ + xhtmlFormat.allowedAttribs = {}; + + /** + * Attributes that are not allowed. + * + * Only used if allowed attributes is null or empty. + * @type {Object} + * @name jQuery.sceditor.plugins.xhtml.disallowedAttribs + * @since v1.4.1 + */ + xhtmlFormat.disallowedAttribs = {}; + + /** + * Array containing all the allowed tags. + * + * If null or empty all tags will be allowed. + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.allowedTags + * @since v1.4.1 + */ + xhtmlFormat.allowedTags = []; + + /** + * Array containing all the disallowed tags. + * + * Only used if allowed tags is null or empty. + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.disallowedTags + * @since v1.4.1 + */ + xhtmlFormat.disallowedTags = []; + + /** + * Array containing tags which should not be removed when empty. + * + * @type {Array} + * @name jQuery.sceditor.plugins.xhtml.allowedEmptyTags + * @since v2.0.0 + */ + xhtmlFormat.allowedEmptyTags = []; + + sceditor.formats.xhtml = xhtmlFormat; +}(sceditor)); diff --git a/public/assets/development/plugins/autosave.js b/public/assets/development/plugins/autosave.js new file mode 100644 index 0000000..950aca2 --- /dev/null +++ b/public/assets/development/plugins/autosave.js @@ -0,0 +1,105 @@ +/** + * SCEditor AutoSave Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (sceditor) { + 'use strict'; + + var defaultKey = 'sce-autodraft-' + location.pathname + location.search; + + function clear(key) { + localStorage.removeItem(key || defaultKey); + } + + sceditor.plugins.autosave = function () { + var base = this; + var editor; + var storageKey = defaultKey; + // 86400000 = 24 hrs (24 * 60 * 60 * 1000) + var expires = 86400000; + var saveHandler = function (value) { + localStorage.setItem(storageKey, JSON.stringify(value)); + }; + var loadHandler = function () { + return JSON.parse(localStorage.getItem(storageKey)); + }; + + function gc() { + for (var i = 0; i < localStorage.length; i++) { + var key = localStorage.key(i); + + if (/^sce\-autodraft\-/.test(key)) { + var item = JSON.parse(localStorage.getItem(storageKey)); + if (item && item.time < Date.now() - expires) { + clear(key); + } + } + } + } + + base.init = function () { + editor = this; + var opts = editor.opts && editor.opts.autosave || {}; + + saveHandler = opts.save || saveHandler; + loadHandler = opts.load || loadHandler; + storageKey = opts.storageKey || storageKey; + expires = opts.expires || expires; + + gc(); + }; + + base.signalReady = function () { + // Add submit event listener to clear autosave + var parent = editor.getContentAreaContainer(); + while (parent) { + if (/form/i.test(parent.nodeName)) { + parent.addEventListener( + 'submit', clear.bind(null, storageKey), true + ); + break; + } + + parent = parent.parentNode; + } + + var state = loadHandler(); + if (state) { + editor.sourceMode(state.sourceMode); + editor.val(state.value, false); + editor.focus(); + + if (state.sourceMode) { + editor.sourceEditorCaret(state.caret); + } else { + editor.getRangeHelper().restoreRange(); + } + } + + saveHandler({ + caret: this.sourceEditorCaret(), + sourceMode: this.sourceMode(), + value: editor.val(null, false), + time: Date.now() + }); + }; + + base.signalValuechangedEvent = function (e) { + saveHandler({ + caret: this.sourceEditorCaret(), + sourceMode: this.sourceMode(), + value: e.detail.rawValue, + time: Date.now() + }); + }; + }; + + sceditor.plugins.autosave.clear = clear; +}(sceditor)); diff --git a/public/assets/development/plugins/autoyoutube.js b/public/assets/development/plugins/autoyoutube.js new file mode 100644 index 0000000..3f3ea39 --- /dev/null +++ b/public/assets/development/plugins/autoyoutube.js @@ -0,0 +1,94 @@ +/** + * SCEditor Auto Youtube Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2016, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (document, sceditor) { + 'use strict'; + + var dom = sceditor.dom; + + /* + (^|\s) Start of line or space + (?:https?:\/\/)? Optional scheme like http:// + (?:www\.)? Optional www. prefix + (?: + youtu\.be\/ Ends with .be/ so whatever comes next is the ID + | + youtube\.com\/watch\?v= Matches the .com version + ) + ([^"&?\/ ]{11}) The actual YT ID + (?:\&[\&_\?0-9a-z\#]+)? Any extra URL params + (\s|$) End of line or space + */ + var ytUrlRegex = /(^|\s)(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/watch\?v=)([^"&?\/ ]{11})(?:\&[\&_\?0-9a-z\#]+)?(\s|$)/i; + + function youtubeEmbedCode(id) { + return ''; + } + + function convertYoutubeLinks(root) { + var node = root.firstChild; + + while (node) { + // 3 is TextNodes + if (node.nodeType === 3) { + var text = node.nodeValue; + var parent = node.parentNode; + var match = text.match(ytUrlRegex); + + if (match) { + parent.insertBefore(document.createTextNode( + text.substr(0, match.index) + match[1] + ), node); + + parent.insertBefore( + dom.parseHTML(youtubeEmbedCode(match[2])), node + ); + + node.nodeValue = match[3] + + text.substr(match.index + match[0].length); + } + } else { + // TODO: Make this tag configurable. + if (!dom.is(node, 'code')) { + convertYoutubeLinks(node); + } + } + + node = node.nextSibling; + } + }; + + sceditor.plugins.autoyoutube = function () { + this.signalPasteRaw = function (data) { + // TODO: Make this tag configurable. + // Skip code tags + if (dom.closest(this.currentNode(), 'code')) { + return; + } + + if (data.html || data.text) { + var html = document.createElement('div'); + + if (data.html) { + html.innerHTML = data.html; + } else { + html.textContent = data.text; + } + + convertYoutubeLinks(html); + + data.html = html.innerHTML; + } + }; + }; +})(document, sceditor); diff --git a/public/assets/development/plugins/dragdrop.js b/public/assets/development/plugins/dragdrop.js new file mode 100644 index 0000000..b050309 --- /dev/null +++ b/public/assets/development/plugins/dragdrop.js @@ -0,0 +1,222 @@ +/** + * SCEditor Drag and Drop Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (sceditor) { + 'use strict'; + + /** + * Place holder GIF shown while image is loading. + * @type {string} + * @private + */ + var loadingGif = 'data:image/gif;base64,R0lGODlhlgBkAPABAH19ffb29iH5BAAK' + + 'AAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQA' + + 'AACwAAAAAlgBkAAAC1YyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s' + + '73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0u' + + 'v2OvwD2fP6iD/gH6Pc2GIhg2JeQSNjGuLf4GMlYKIloefAIUEl52ZmJyaY5mUhqyFnq' + + 'mQr6KRoaMKp66hbLumpQ69oK+5qrOyg4a6qYV2x8jJysvMzc7PwMHS09TV1tfY2drb3' + + 'N3e39DR4uPk5ebn6Onq6+zt7u/g4fL99UAAAh+QQACgAAACwAAAAAlgBkAIEAAAB9fX' + + '329vYAAAAC3JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MC' + + 'ofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwD2' + + 'fP4iABgY+CcoCNeHuJdQyLjIaOiWiOj4CEhZ+SbZd/nI2RipqYhQOThKGpAZCuBZyAr' + + 'ZprpqSupaCqtaazmLCRqai7rb2av5W5wqSShcm8fc7PwMHS09TV1tfY2drb3N3e39DR' + + '4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5/vVAAAIfkEAAoAAAAsAAAAAJYAZACBAAAAf' + + 'X199vb2AAAAAuCUj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/4P' + + 'DAqHxKLxiEwql8ym8wmNSqfUqvWKzWq33K73Cw6Lx+Sy+YxOq9fstvsNj8vn9Lr9jr8' + + 'E9nz+AgAYGLjQVwhXiJgguAiYgGjo9tinyCjoKLn3hpmJUGmJsBmguUnpCXCJOZraaX' + + 'oKShoJe9DqehCqKlnqiZobuzrbyvuIO8xqKpxIPKlwrPCbBx0tPU1dbX2Nna29zd3t/' + + 'Q0eLj5OXm5+jp6uvs7e7v4OHy8/T19vf4+fr7/P379UAAAh+QQACgAAACwAAAAAlgBk' + + 'AIEAAAB9fX329vYAAAAC4JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3' + + 'n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+' + + 'f0uv2OvwT2fP6iD7gAMEhICAeImIAYiFDoOPi22KcouZfw6BhZGUBZeYlp6LbJiTD6C' + + 'Qqg6Vm6eQqqKtkZ24iaKtrKunpQa9tmmju7Wwu7KFtMi3oYDMzompkHHS09TV1tfY2d' + + 'rb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f31QAADs='; + + /** + * Basic check for browser support + * @type {boolean} + * @private + */ + var isSupported = typeof window.FileReader !== 'undefined'; + var base64DataUri = /data:[^;]+;base64,/i; + + function base64DataUriToBlob(url) { + // 5 is length of "data:" prefix + var mime = url.substr(5, url.indexOf(';') - 5); + var data = atob(url.substr(url.indexOf(',') + 1)); + /* global Uint8Array */ + var binary = new Uint8Array(data.length); + + for (var i = 0; i < data.length; i++) { + binary[i] = data[i].charCodeAt(0); + } + + try { + return new Blob([binary], { type: mime }); + } catch (e) { + return null; + } + } + + sceditor.plugins.dragdrop = function () { + if (!isSupported) { + return; + } + + var base = this; + var opts; + var editor; + var handleFile; + var container; + var cover; + var placeholderId = 0; + + + function hideCover() { + cover.style.display = 'none'; + container.className = container.className.replace(/(^| )dnd( |$)/g, ''); + } + + function showCover() { + if (cover.style.display === 'none') { + cover.style.display = 'block'; + container.className += ' dnd'; + } + } + + function isAllowed(file) { + // FF sets type to application/x-moz-file until it has been dropped + if (file.type !== 'application/x-moz-file' && opts.allowedTypes && + opts.allowedTypes.indexOf(file.type) < 0) { + return false; + } + + return opts.isAllowed ? opts.isAllowed(file) : true; + }; + + function createHolder(toReplace) { + var placeholder = document.createElement('img'); + placeholder.src = loadingGif; + placeholder.className = 'sceditor-ignore'; + placeholder.id = 'sce-dragdrop-' + placeholderId++; + + function replace(html) { + var node = editor + .getBody() + .ownerDocument + .getElementById(placeholder.id); + + if (node) { + if (typeof html === 'string') { + node.insertAdjacentHTML('afterend', html); + } + + node.parentNode.removeChild(node); + } + } + + return function () { + if (toReplace) { + toReplace.parentNode.replaceChild(placeholder, toReplace); + } else { + editor.wysiwygEditorInsertHtml(placeholder.outerHTML); + } + + return { + insert: function (html) { + replace(html); + }, + cancel: replace + }; + }; + } + + function handleDragOver(e) { + var dt = e.dataTransfer; + var files = dt.files.length || !dt.items ? dt.files : dt.items; + + for (var i = 0; i < files.length; i++) { + // Dragging a string should be left to default + if (files[i].kind === 'string') { + return; + } + } + + showCover(); + e.preventDefault(); + } + + function handleDrop(e) { + var dt = e.dataTransfer; + var files = dt.files.length || !dt.items ? dt.files : dt.items; + + hideCover(); + + for (var i = 0; i < files.length; i++) { + // Dragging a string should be left to default + if (files[i].kind === 'string') { + return; + } + + if (isAllowed(files[i])) { + handleFile(files[i], createHolder()); + } + } + + e.preventDefault(); + } + + base.signalReady = function () { + editor = this; + opts = editor.opts.dragdrop || {}; + handleFile = opts.handleFile; + + container = editor.getContentAreaContainer().parentNode; + + cover = container.appendChild(sceditor.dom.parseHTML( + '' + ).firstChild); + + container.addEventListener('dragover', handleDragOver); + container.addEventListener('dragleave', hideCover); + container.addEventListener('dragend', hideCover); + container.addEventListener('drop', handleDrop); + + editor.getBody().addEventListener('dragover', handleDragOver); + editor.getBody().addEventListener('drop', hideCover); + }; + + base.signalPasteHtml = function (paste) { + if (!('handlePaste' in opts) || opts.handlePaste) { + var div = document.createElement('div'); + div.innerHTML = paste.val; + + var images = div.querySelectorAll('img'); + for (var i = 0; i < images.length; i++) { + var image = images[i]; + + if (base64DataUri.test(image.src)) { + var file = base64DataUriToBlob(image.src); + if (file && isAllowed(file)) { + handleFile(file, createHolder(image)); + } else { + image.parentNode.removeChild(image); + } + } + } + + paste.val = div.innerHTML; + } + }; + }; +})(sceditor); diff --git a/public/assets/development/plugins/format.js b/public/assets/development/plugins/format.js new file mode 100644 index 0000000..bcf4476 --- /dev/null +++ b/public/assets/development/plugins/format.js @@ -0,0 +1,127 @@ +/** + * SCEditor Paragraph Formatting Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2011-2013, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor Paragraph Formatting Plugin + * @author Sam Clarke + */ +(function (sceditor) { + 'use strict'; + + sceditor.plugins.format = function () { + var base = this; + + /** + * Default tags + * @type {Object} + * @private + */ + var tags = { + p: 'Paragraph', + h1: 'Heading 1', + h2: 'Heading 2', + h3: 'Heading 3', + h4: 'Heading 4', + h5: 'Heading 5', + h6: 'Heading 6', + address: 'Address', + pre: 'Preformatted Text' + }; + + /** + * Private functions + * @private + */ + var insertTag, + formatCmd; + + + base.init = function () { + var opts = this.opts, + pOpts = opts.paragraphformat; + + // Don't enable if the BBCode plugin is enabled. + if (opts.format && opts.format === 'bbcode') { + return; + } + + if (pOpts) { + if (pOpts.tags) { + tags = pOpts.tags; + } + + if (pOpts.excludeTags) { + pOpts.excludeTags.forEach(function (val) { + delete tags[val]; + }); + } + } + + if (!this.commands.format) { + this.commands.format = { + exec: formatCmd, + txtExec: formatCmd, + tooltip: 'Format Paragraph' + }; + } + + if (opts.toolbar === sceditor.defaultOptions.toolbar) { + opts.toolbar = opts.toolbar.replace(',color,', + ',color,format,'); + } + }; + + /** + * Inserts the specified tag into the editor + * + * @param {sceditor} editor + * @param {string} tag + * @private + */ + insertTag = function (editor, tag) { + if (editor.sourceMode()) { + editor.insert('<' + tag + '>', ''); + } else { + editor.execCommand('formatblock', '<' + tag + '>'); + } + + }; + + /** + * Function for the exec and txtExec properties + * + * @param {node} caller + * @private + */ + formatCmd = function (caller) { + var editor = this, + content = document.createElement('div'); + + sceditor.utils.each(tags, function (tag, val) { + var link = document.createElement('a'); + link.className = 'sceditor-option'; + link.textContent = val.name || val; + link.addEventListener('click', function (e) { + editor.closeDropDown(true); + + if (val.exec) { + val.exec(editor); + } else { + insertTag(editor, tag); + } + + e.preventDefault(); + }); + + content.appendChild(link); + }); + + editor.createDropDown(caller, 'format', content); + }; + }; +})(sceditor); diff --git a/public/assets/development/plugins/plaintext.js b/public/assets/development/plugins/plaintext.js new file mode 100644 index 0000000..7b2bfc6 --- /dev/null +++ b/public/assets/development/plugins/plaintext.js @@ -0,0 +1,59 @@ +/** + * SCEditor Plain Text Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2016, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +(function (sceditor) { + 'use strict'; + + var extend = sceditor.utils.extend; + + /** + * Options: + * + * pastetext.addButton - If to replace the plaintext button with a toggle + * button that enables and disables plain text mode. + * + * pastetext.enabled - If the plain text button should be enabled at start + * up. Only applies if addButton is enabled. + */ + sceditor.plugins.plaintext = function () { + var plainTextEnabled = true; + + this.init = function () { + var commands = this.commands; + var opts = this.opts; + + if (opts && opts.plaintext && opts.plaintext.addButton) { + plainTextEnabled = opts.plaintext.enabled; + + commands.pastetext = extend(commands.pastetext || {}, { + state: function () { + return plainTextEnabled ? 1 : 0; + }, + exec: function () { + plainTextEnabled = !plainTextEnabled; + } + }); + } + }; + + this.signalPasteRaw = function (data) { + if (plainTextEnabled) { + if (data.html && !data.text) { + var div = document.createElement('div'); + div.innerHTML = data.html; + data.text = div.innerText; + } + + data.html = null; + } + }; + }; +}(sceditor)); diff --git a/public/assets/development/plugins/strictbbcode.js b/public/assets/development/plugins/strictbbcode.js new file mode 100644 index 0000000..da23bbf --- /dev/null +++ b/public/assets/development/plugins/strictbbcode.js @@ -0,0 +1,21 @@ +/** + * SCEditor Strict BBCode Plugin + * http://www.sceditor.com/ + * + * Copyright (C) 2016, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Sam Clarke + */ +// (function (sceditor) { +// 'use strict'; + +// // var extend = sceditor.utils.extend; + +// sceditor.plugins.strictbbcode = function () { +// // override bbcode plugin add and update default bbcodes to have attrs +// // and override their exec funcs +// }; +// }(sceditor)); diff --git a/public/assets/development/plugins/undo.js b/public/assets/development/plugins/undo.js new file mode 100644 index 0000000..2974598 --- /dev/null +++ b/public/assets/development/plugins/undo.js @@ -0,0 +1,187 @@ +(function (sceditor) { + 'use strict'; + + sceditor.plugins.undo = function () { + var base = this; + var editor; + var charChangedCount = 0; + var previousValue; + + var undoLimit = 50; + var redoStates = []; + var undoStates = []; + var ignoreNextValueChanged = false; + + /** + * Sets the editor to the specified state. + * + * @param {Object} state + * @private + */ + var applyState = function (state) { + ignoreNextValueChanged = true; + + previousValue = state.value; + + editor.sourceMode(state.sourceMode); + editor.val(state.value, false); + editor.focus(); + + if (state.sourceMode) { + editor.sourceEditorCaret(state.caret); + } else { + editor.getRangeHelper().restoreRange(); + } + + ignoreNextValueChanged = false; + }; + + + /** + * Calculates the number of characters that have changed + * between two strings. + * + * @param {String} strA + * @param {String} strB + * @return {String} + * @private + */ + var simpleDiff = function (strA, strB) { + var start, end, aLenDiff, bLenDiff, + aLength = strA.length, + bLength = strB.length, + length = Math.max(aLength, bLength); + + // Calculate the start + for (start = 0; start < length; start++) { + if (strA.charAt(start) !== strB.charAt(start)) { + break; + } + } + + // Calculate the end + aLenDiff = aLength < bLength ? bLength - aLength : 0; + bLenDiff = bLength < aLength ? aLength - bLength : 0; + + for (end = length - 1; end >= 0; end--) { + if (strA.charAt(end - aLenDiff) !== + strB.charAt(end - bLenDiff)) { + break; + } + } + + return (end - start) + 1; + }; + + base.init = function () { + // The this variable will be set to the instance of the editor + // calling it, hence why the plugins "this" is saved to the base + // variable. + editor = this; + + undoLimit = editor.undoLimit || undoLimit; + + // addShortcut is the easiest way to add handlers to specific + // shortcuts + editor.addShortcut('ctrl+z', base.undo); + editor.addShortcut('ctrl+shift+z', base.redo); + editor.addShortcut('ctrl+y', base.redo); + }; + + base.undo = function () { + var state = undoStates.pop(); + var rawEditorValue = editor.val(null, false); + + if (state && !redoStates.length && rawEditorValue === state.value) { + state = undoStates.pop(); + } + + if (state) { + if (!redoStates.length) { + redoStates.push({ + 'caret': editor.sourceEditorCaret(), + 'sourceMode': editor.sourceMode(), + 'value': rawEditorValue + }); + } + + redoStates.push(state); + applyState(state); + } + + return false; + }; + + base.redo = function () { + var state = redoStates.pop(); + + if (!undoStates.length) { + undoStates.push(state); + state = redoStates.pop(); + } + + if (state) { + undoStates.push(state); + applyState(state); + } + + return false; + }; + + base.signalReady = function () { + var rawValue = editor.val(null, false); + + // Store the initial value as the last value + previousValue = rawValue; + + undoStates.push({ + 'caret': this.sourceEditorCaret(), + 'sourceMode': this.sourceMode(), + 'value': rawValue + }); + }; + + /** + * Handle the valueChanged signal. + * + * e.rawValue will either be the raw HTML from the WYSIWYG editor with + * the rangeHelper range markers inserted, or it will be the raw value + * of the source editor (BBCode or HTML depending on plugins). + * @return {void} + */ + base.signalValuechangedEvent = function (e) { + var rawValue = e.detail.rawValue; + + if (undoLimit > 0 && undoStates.length > undoLimit) { + undoStates.shift(); + } + + // If the editor hasn't fully loaded yet, + // then the previous value won't be set. + if (ignoreNextValueChanged || !previousValue || + previousValue === rawValue) { + return; + } + + // Value has changed so remove all redo states + redoStates.length = 0; + charChangedCount += simpleDiff(previousValue, rawValue); + + if (charChangedCount < 20) { + return; + // ?? + } else if (charChangedCount < 50 && !/\s$/g.test(e.rawValue)) { + return; + } + + undoStates.push({ + 'caret': editor.sourceEditorCaret(), + 'sourceMode': editor.sourceMode(), + 'value': rawValue + }); + + charChangedCount = 0; + previousValue = rawValue; + }; + }; +}(sceditor)); diff --git a/public/assets/development/plugins/v1compat.js b/public/assets/development/plugins/v1compat.js new file mode 100644 index 0000000..e67e5f3 --- /dev/null +++ b/public/assets/development/plugins/v1compat.js @@ -0,0 +1,97 @@ +/** + * Version 1 compatibility plugin + * + * Patches commands and BBCodes set with + * command.set and bbcode.set to wrap DOM + * node arguments in jQuery objects. + * + * Should only be used to ease migrating. + */ +(function (sceditor, $) { + 'use strict'; + + var plugins = sceditor.plugins; + + /** + * Patches a method to wrap and DOM nodes in a jQuery object + * @private + */ + function patchMethodArguments(fn) { + if (fn._scePatched) { + return fn; + } + + var patch = function () { + var args = []; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + + if (arg && arg.nodeType) { + args.push($(arg)); + } else { + args.push(arg); + } + } + + return fn.apply(this, args); + }; + + patch._scePatched = true; + return patch; + } + + /** + * Patches a method to wrap any return value in a jQuery object + * @private + */ + function patchMethodReturn(fn) { + if (fn._scePatched) { + return fn; + } + + var patch = function () { + return $(fn.apply(this, arguments)); + }; + + patch._scePatched = true; + return patch; + } + + var oldSet = sceditor.command.set; + sceditor.command.set = function (name, cmd) { + if (cmd && typeof cmd.exec === 'function') { + cmd.exec = patchMethodArguments(cmd.exec); + } + + if (cmd && typeof cmd.txtExec === 'function') { + cmd.txtExec = patchMethodArguments(cmd.txtExec); + } + + return oldSet.call(this, name, cmd); + }; + + if (plugins.bbcode) { + var oldBBCodeSet = plugins.bbcode.bbcode.set; + plugins.bbcode.bbcode.set = function (name, bbcode) { + if (bbcode && typeof bbcode.format === 'function') { + bbcode.format = patchMethodArguments(bbcode.format); + } + + return oldBBCodeSet.call(this, name, bbcode); + }; + }; + + var oldCreate = sceditor.create; + sceditor.create = function (textarea, options) { + oldCreate.call(this, textarea, options); + + if (textarea && textarea._sceditor) { + var editor = textarea._sceditor; + + editor.getBody = patchMethodReturn(editor.getBody); + editor.getContentAreaContainer = + patchMethodReturn(editor.getContentAreaContainer); + } + }; +}(sceditor, jQuery)); diff --git a/public/assets/development/sceditor.js b/public/assets/development/sceditor.js new file mode 100644 index 0000000..1d4410d --- /dev/null +++ b/public/assets/development/sceditor.js @@ -0,0 +1,7571 @@ +(function () { + 'use strict'; + + /** + * Check if the passed argument is the + * the passed type. + * + * @param {string} type + * @param {*} arg + * @returns {boolean} + */ + function isTypeof(type, arg) { + return typeof arg === type; + } + + /** + * @type {function(*): boolean} + */ + var isString = isTypeof.bind(null, 'string'); + + /** + * @type {function(*): boolean} + */ + var isUndefined = isTypeof.bind(null, 'undefined'); + + /** + * @type {function(*): boolean} + */ + var isFunction = isTypeof.bind(null, 'function'); + + /** + * @type {function(*): boolean} + */ + var isNumber = isTypeof.bind(null, 'number'); + + + /** + * Returns true if an object has no keys + * + * @param {!Object} obj + * @returns {boolean} + */ + function isEmptyObject(obj) { + return !Object.keys(obj).length; + } + + /** + * Extends the first object with any extra objects passed + * + * If the first argument is boolean and set to true + * it will extend child arrays and objects recursively. + * + * @param {!Object|boolean} targetArg + * @param {...Object} source + * @return {Object} + */ + function extend(targetArg, sourceArg) { + var isTargetBoolean = targetArg === !!targetArg; + var i = isTargetBoolean ? 2 : 1; + var target = isTargetBoolean ? sourceArg : targetArg; + var isDeep = isTargetBoolean ? targetArg : false; + + for (; i < arguments.length; i++) { + var source = arguments[i]; + + // Copy all properties for jQuery compatibility + /* eslint guard-for-in: off */ + for (var key in source) { + var value = source[key]; + + // Skip undefined values to match jQuery and + // skip if target to prevent infinite loop + if (!isUndefined(value)) { + var isObject = value !== null && typeof value === 'object' && + Object.getPrototypeOf(value) === Object.prototype; + var isArray = Array.isArray(value); + + if (isDeep && (isObject || isArray)) { + target[key] = extend( + true, + target[key] || (isArray ? [] : {}), + value + ); + } else { + target[key] = value; + } + } + } + } + + return target; + } + + /** + * Removes an item from the passed array + * + * @param {!Array} arr + * @param {*} item + */ + function arrayRemove(arr, item) { + var i = arr.indexOf(item); + + if (i > -1) { + arr.splice(i, 1); + } + } + + /** + * Iterates over an array or object + * + * @param {!Object|Array} obj + * @param {function(*, *)} fn + */ + function each(obj, fn) { + if (Array.isArray(obj) || 'length' in obj && isNumber(obj.length)) { + for (var i = 0; i < obj.length; i++) { + fn(i, obj[i]); + } + } else { + Object.keys(obj).forEach(function (key) { + fn(key, obj[key]); + }); + } + } + + /** + * Cache of camelCase CSS property names + * @type {Object} + */ + var cssPropertyNameCache = {}; + + /** + * Node type constant for element nodes + * + * @type {number} + */ + var ELEMENT_NODE = 1; + + /** + * Node type constant for text nodes + * + * @type {number} + */ + var TEXT_NODE = 3; + + /** + * Node type constant for comment nodes + * + * @type {number} + */ + + + /** + * Node type document nodes + * + * @type {number} + */ + + + /** + * Node type constant for document fragments + * + * @type {number} + */ + + + function toFloat(value) { + value = parseFloat(value); + + return isFinite(value) ? value : 0; + } + + /** + * Creates an element with the specified attributes + * + * Will create it in the current document unless context + * is specified. + * + * @param {!string} tag + * @param {!Object} [attributes] + * @param {!Document} [context] + * @returns {!HTMLElement} + */ + function createElement(tag, attributes, context) { + var node = (context || document).createElement(tag); + + each(attributes || {}, function (key, value) { + if (key === 'style') { + node.style.cssText = value; + } else if (key in node) { + node[key] = value; + } else { + node.setAttribute(key, value); + } + }); + + return node; + } + + /** + * Returns an array of parents that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {Array} + */ + + + /** + * Gets the first parent node that matches the selector + * + * @param {!HTMLElement} node + * @param {!string} [selector] + * @returns {HTMLElement|undefined} + */ + function parent(node, selector) { + var parent = node || {}; + + while ((parent = parent.parentNode) && !/(9|11)/.test(parent.nodeType)) { + if (!selector || is(parent, selector)) { + return parent; + } + } + } + + /** + * Checks the passed node and all parents and + * returns the first matching node if any. + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {HTMLElement|undefined} + */ + function closest(node, selector) { + return is(node, selector) ? node : parent(node, selector); + } + + /** + * Removes the node from the DOM + * + * @param {!HTMLElement} node + */ + function remove(node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } + } + + /** + * Appends child to parent node + * + * @param {!HTMLElement} node + * @param {!HTMLElement} child + */ + function appendChild(node, child) { + node.appendChild(child); + } + + /** + * Finds any child nodes that match the selector + * + * @param {!HTMLElement} node + * @param {!string} selector + * @returns {NodeList} + */ + function find(node, selector) { + return node.querySelectorAll(selector); + } + + /** + * For on() and off() if to add/remove the event + * to the capture phase + * + * @type {boolean} + */ + var EVENT_CAPTURE = true; + + /** + * For on() and off() if to add/remove the event + * to the bubble phase + * + * @type {boolean} + */ + + + /** + * Adds an event listener for the specified events. + * + * Events should be a space separated list of events. + * + * If selector is specified the handler will only be + * called when the event target matches the selector. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see off() + */ + // eslint-disable-next-line max-params + function on(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector] || function (e) { + var target = e.target; + while (target && target !== node) { + if (is(target, selector)) { + fn.call(target, e); + return; + } + + target = target.parentNode; + } + }; + + fn['_sce-event-' + event + selector] = handler; + } else { + handler = selector; + capture = fn; + } + + node.addEventListener(event, handler, capture || false); + }); + } + + /** + * Removes an event listener for the specified events. + * + * @param {!Node} node + * @param {string} events + * @param {string} [selector] + * @param {function(Object)} fn + * @param {boolean} [capture=false] + * @see on() + */ + // eslint-disable-next-line max-params + function off(node, events, selector, fn, capture) { + events.split(' ').forEach(function (event) { + var handler; + + if (isString(selector)) { + handler = fn['_sce-event-' + event + selector]; + } else { + handler = selector; + capture = fn; + } + + node.removeEventListener(event, handler, capture || false); + }); + } + + /** + * If only attr param is specified it will get + * the value of the attr param. + * + * If value is specified but null the attribute + * will be removed otherwise the attr value will + * be set to the passed value. + * + * @param {!HTMLElement} node + * @param {!string} attr + * @param {?string} [value] + */ + function attr(node, attr, value) { + if (arguments.length < 3) { + return node.getAttribute(attr); + } + + // eslint-disable-next-line eqeqeq, no-eq-null + if (value == null) { + removeAttr(node, attr); + } else { + node.setAttribute(attr, value); + } + } + + /** + * Removes the specified attribute + * + * @param {!HTMLElement} node + * @param {!string} attr + */ + function removeAttr(node, attr) { + node.removeAttribute(attr); + } + + /** + * Sets the passed elements display to none + * + * @param {!HTMLElement} node + */ + function hide(node) { + css(node, 'display', 'none'); + } + + /** + * Sets the passed elements display to default + * + * @param {!HTMLElement} node + */ + function show(node) { + css(node, 'display', ''); + } + + /** + * Toggles an elements visibility + * + * @param {!HTMLElement} node + */ + function toggle(node) { + if (isVisible(node)) { + hide(node); + } else { + show(node); + } + } + + /** + * Gets a computed CSS values or sets an inline CSS value + * + * Rules should be in camelCase format and not + * hyphenated like CSS properties. + * + * @param {!HTMLElement} node + * @param {!Object|string} rule + * @param {string|number} [value] + * @return {string|number|undefined} + */ + function css(node, rule, value) { + if (arguments.length < 3) { + if (isString(rule)) { + return node.nodeType === 1 ? getComputedStyle(node)[rule] : null; + } + + each(rule, function (key, value) { + css(node, key, value); + }); + } else { + // isNaN returns false for null, false and empty strings + // so need to check it's truthy or 0 + var isNumeric = (value || value === 0) && !isNaN(value); + node.style[rule] = isNumeric ? value + 'px' : value; + } + } + + + /** + * Gets or sets thee data attributes on a node + * + * Unlike the jQuery version this only stores data + * in the DOM attributes which means only strings + * can be stored. + * + * @param {Node} node + * @param {string} [key] + * @param {string} [value] + * @return {Object|undefined} + */ + function data(node, key, value) { + var argsLength = arguments.length; + var data = {}; + + if (node.nodeType === ELEMENT_NODE) { + if (argsLength === 1) { + each(node.attributes, function (_, attr) { + if (/^data\-/i.test(attr.name)) { + data[attr.name.substr(5)] = attr.value; + } + }); + + return data; + } + + if (argsLength === 2) { + return attr(node, 'data-' + key); + } + + attr(node, 'data-' + key, String(value)); + } + } + + /** + * Checks if node matches the given selector. + * + * @param {?HTMLElement} node + * @param {string} selector + * @returns {boolean} + */ + function is(node, selector) { + var result = false; + + if (node && node.nodeType === ELEMENT_NODE) { + result = (node.matches || node.msMatchesSelector || + node.webkitMatchesSelector).call(node, selector); + } + + return result; + } + + + /** + * Returns true if node contains child otherwise false. + * + * This differs from the DOM contains() method in that + * if node and child are equal this will return false. + * + * @param {!Node} node + * @param {HTMLElement} child + * @returns {boolean} + */ + function contains(node, child) { + return node !== child && node.contains && node.contains(child); + } + + /** + * @param {Node} node + * @param {string} [selector] + * @returns {?HTMLElement} + */ + function previousElementSibling(node, selector) { + var prev = node.previousElementSibling; + + if (selector && prev) { + return is(prev, selector) ? prev : null; + } + + return prev; + } + + /** + * @param {!Node} node + * @param {!Node} refNode + * @returns {Node} + */ + function insertBefore(node, refNode) { + return refNode.parentNode.insertBefore(node, refNode); + } + + /** + * @param {?HTMLElement} node + * @returns {!Array.} + */ + function classes(node) { + return node.className.trim().split(/\s+/); + } + + /** + * @param {?HTMLElement} node + * @param {string} className + * @returns {boolean} + */ + function hasClass(node, className) { + return is(node, '.' + className); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function addClass(node, className) { + var classList = classes(node); + + if (classList.indexOf(className) < 0) { + classList.push(className); + } + + node.className = classList.join(' '); + } + + /** + * @param {!HTMLElement} node + * @param {string} className + */ + function removeClass(node, className) { + var classList = classes(node); + + arrayRemove(classList, className); + + node.className = classList.join(' '); + } + + /** + * Toggles a class on node. + * + * If state is specified and is truthy it will add + * the class. + * + * If state is specified and is falsey it will remove + * the class. + * + * @param {HTMLElement} node + * @param {string} className + * @param {boolean} [state] + */ + function toggleClass(node, className, state) { + state = isUndefined(state) ? !hasClass(node, className) : state; + + if (state) { + addClass(node, className); + } else { + removeClass(node, className); + } + } + + /** + * Gets or sets the width of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function width(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingLeft) + toFloat(cs.paddingRight); + var border = toFloat(cs.borderLeftWidth) + toFloat(cs.borderRightWidth); + + return node.offsetWidth - padding - border; + } + + css(node, 'width', value); + } + + /** + * Gets or sets the height of the passed node. + * + * @param {HTMLElement} node + * @param {number|string} [value] + * @returns {number|undefined} + */ + function height(node, value) { + if (isUndefined(value)) { + var cs = getComputedStyle(node); + var padding = toFloat(cs.paddingTop) + toFloat(cs.paddingBottom); + var border = toFloat(cs.borderTopWidth) + toFloat(cs.borderBottomWidth); + + return node.offsetHeight - padding - border; + } + + css(node, 'height', value); + } + + /** + * Triggers a custom event with the specified name and + * sets the detail property to the data object passed. + * + * @param {HTMLElement} node + * @param {string} eventName + * @param {Object} [data] + */ + function trigger(node, eventName, data) { + var event; + + if (isFunction(window.CustomEvent)) { + event = new CustomEvent(eventName, { + bubbles: true, + cancelable: true, + detail: data + }); + } else { + event = node.ownerDocument.createEvent('CustomEvent'); + event.initCustomEvent(eventName, true, true, data); + } + + node.dispatchEvent(event); + } + + /** + * Returns if a node is visible. + * + * @param {HTMLElement} + * @returns {boolean} + */ + function isVisible(node) { + return !!node.getClientRects().length; + } + + /** + * Convert CSS property names into camel case + * + * @param {string} string + * @returns {string} + */ + function camelCase(string) { + return string + .replace(/^-ms-/, 'ms-') + .replace(/-(\w)/g, function (match, char) { + return char.toUpperCase(); + }); + } + + + /** + * Loop all child nodes of the passed node + * + * The function should accept 1 parameter being the node. + * If the function returns false the loop will be exited. + * + * @param {HTMLElement} node + * @param {function} func Callback which is called with every + * child node as the first argument. + * @param {boolean} innermostFirst If the innermost node should be passed + * to the function before it's parents. + * @param {boolean} siblingsOnly If to only traverse the nodes siblings + * @param {boolean} [reverse=false] If to traverse the nodes in reverse + */ + // eslint-disable-next-line max-params + function traverse(node, func, innermostFirst, siblingsOnly, reverse) { + node = reverse ? node.lastChild : node.firstChild; + + while (node) { + var next = reverse ? node.previousSibling : node.nextSibling; + + if ( + (!innermostFirst && func(node) === false) || + (!siblingsOnly && traverse( + node, func, innermostFirst, siblingsOnly, reverse + ) === false) || + (innermostFirst && func(node) === false) + ) { + return false; + } + + node = next; + } + } + + /** + * Like traverse but loops in reverse + * @see traverse + */ + function rTraverse(node, func, innermostFirst, siblingsOnly) { + traverse(node, func, innermostFirst, siblingsOnly, true); + } + + /** + * Parses HTML into a document fragment + * + * @param {string} html + * @param {Document} [context] + * @since 1.4.4 + * @return {DocumentFragment} + */ + function parseHTML(html, context) { + context = context || document; + + var ret = context.createDocumentFragment(); + var tmp = createElement('div', {}, context); + + tmp.innerHTML = html; + + while (tmp.firstChild) { + appendChild(ret, tmp.firstChild); + } + + return ret; + } + + /** + * Checks if an element has any styling. + * + * It has styling if it is not a plain
    or

    or + * if it has a class, style attribute or data. + * + * @param {HTMLElement} elm + * @return {boolean} + * @since 1.4.4 + */ + function hasStyling(node) { + return node && (!is(node, 'p,div') || node.className || + attr(node, 'style') || !isEmptyObject(data(node))); + } + + /** + * Converts an element from one type to another. + * + * For example it can convert the element to + * + * @param {HTMLElement} element + * @param {string} toTagName + * @return {HTMLElement} + * @since 1.4.4 + */ + function convertElement(element, toTagName) { + var newElement = createElement(toTagName, {}, element.ownerDocument); + + each(element.attributes, function (_, attribute) { + // Some browsers parse invalid attributes names like + // 'size"2' which throw an exception when set, just + // ignore these. + try { + attr(newElement, attribute.name, attribute.value); + } catch (ex) {} + }); + + while (element.firstChild) { + appendChild(newElement, element.firstChild); + } + + element.parentNode.replaceChild(newElement, element); + + return newElement; + } + + /** + * List of block level elements separated by bars (|) + * + * @type {string} + */ + var blockLevelList = '|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|' + + 'form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|'; + + /** + * List of elements that do not allow children separated by bars (|) + * + * @param {Node} node + * @return {boolean} + * @since 1.4.5 + */ + function canHaveChildren(node) { + // 1 = Element + // 9 = Document + // 11 = Document Fragment + if (!/11?|9/.test(node.nodeType)) { + return false; + } + + // List of empty HTML tags separated by bar (|) character. + // Source: http://www.w3.org/TR/html4/index/elements.html + // Source: http://www.w3.org/TR/html5/syntax.html#void-elements + return ('|iframe|area|base|basefont|br|col|frame|hr|img|input|wbr' + + '|isindex|link|meta|param|command|embed|keygen|source|track|' + + 'object|').indexOf('|' + node.nodeName.toLowerCase() + '|') < 0; + } + + /** + * Checks if an element is inline + * + * @param {HTMLElement} elm + * @param {boolean} [includeCodeAsBlock=false] + * @return {boolean} + */ + function isInline(elm, includeCodeAsBlock) { + var tagName, + nodeType = (elm || {}).nodeType || TEXT_NODE; + + if (nodeType !== ELEMENT_NODE) { + return nodeType === TEXT_NODE; + } + + tagName = elm.tagName.toLowerCase(); + + if (tagName === 'code') { + return !includeCodeAsBlock; + } + + return blockLevelList.indexOf('|' + tagName + '|') < 0; + } + + /** + * Copy the CSS from 1 node to another. + * + * Only copies CSS defined on the element e.g. style attr. + * + * @param {HTMLElement} from + * @param {HTMLElement} to + */ + function copyCSS(from, to) { + to.style.cssText = from.style.cssText + to.style.cssText; + } + + /** + * Fixes block level elements inside in inline elements. + * + * Also fixes invalid list nesting by placing nested lists + * inside the previous li tag or wrapping them in an li tag. + * + * @param {HTMLElement} node + */ + function fixNesting(node) { + var getLastInlineParent = function (node) { + while (isInline(node.parentNode, true)) { + node = node.parentNode; + } + + return node; + }; + + traverse(node, function (node) { + var list = 'ul,ol', + isBlock = !isInline(node, true); + + // Any blocklevel element inside an inline element needs fixing. + if (isBlock && isInline(node.parentNode, true)) { + var parent = getLastInlineParent(node), + before = extractContents(parent, node), + middle = node; + + // copy current styling so when moved out of the parent + // it still has the same styling + copyCSS(parent, middle); + + insertBefore(before, parent); + insertBefore(middle, parent); + } + + // Fix invalid nested lists which should be wrapped in an li tag + if (isBlock && is(node, list) && is(node.parentNode, list)) { + var li = previousElementSibling(node, 'li'); + + if (!li) { + li = createElement('li'); + insertBefore(li, node); + } + + appendChild(li, node); + } + }); + } + + /** + * Finds the common parent of two nodes + * + * @param {!HTMLElement} node1 + * @param {!HTMLElement} node2 + * @return {?HTMLElement} + */ + function findCommonAncestor(node1, node2) { + while ((node1 = node1.parentNode)) { + if (contains(node1, node2)) { + return node1; + } + } + } + + /** + * @param {?Node} + * @param {boolean} [previous=false] + * @returns {?Node} + */ + function getSibling(node, previous) { + if (!node) { + return null; + } + + return (previous ? node.previousSibling : node.nextSibling) || + getSibling(node.parentNode, previous); + } + + /** + * Removes unused whitespace from the root and all it's children. + * + * @param {!HTMLElement} root + * @since 1.4.3 + */ + function removeWhiteSpace(root) { + var nodeValue, nodeType, next, previous, previousSibling, + nextNode, trimStart, + cssWhiteSpace = css(root, 'whiteSpace'), + // Preserve newlines if is pre-line + preserveNewLines = /line$/i.test(cssWhiteSpace), + node = root.firstChild; + + // Skip pre & pre-wrap with any vendor prefix + if (/pre(\-wrap)?$/i.test(cssWhiteSpace)) { + return; + } + + while (node) { + nextNode = node.nextSibling; + nodeValue = node.nodeValue; + nodeType = node.nodeType; + + if (nodeType === ELEMENT_NODE && node.firstChild) { + removeWhiteSpace(node); + } + + if (nodeType === TEXT_NODE) { + next = getSibling(node); + previous = getSibling(node, true); + trimStart = false; + + while (hasClass(previous, 'sceditor-ignore')) { + previous = getSibling(previous, true); + } + + // If previous sibling isn't inline or is a textnode that + // ends in whitespace, time the start whitespace + if (isInline(node) && previous) { + previousSibling = previous; + + while (previousSibling.lastChild) { + previousSibling = previousSibling.lastChild; + + // eslint-disable-next-line max-depth + while (hasClass(previousSibling, 'sceditor-ignore')) { + previousSibling = getSibling(previousSibling, true); + } + } + + trimStart = previousSibling.nodeType === TEXT_NODE ? + /[\t\n\r ]$/.test(previousSibling.nodeValue) : + !isInline(previousSibling); + } + + // Clear zero width spaces + nodeValue = nodeValue.replace(/\u200B/g, ''); + + // Strip leading whitespace + if (!previous || !isInline(previous) || trimStart) { + nodeValue = nodeValue.replace( + preserveNewLines ? /^[\t ]+/ : /^[\t\n\r ]+/, + '' + ); + } + + // Strip trailing whitespace + if (!next || !isInline(next)) { + nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+$/ : /[\t\n\r ]+$/, + '' + ); + } + + // Remove empty text nodes + if (!nodeValue.length) { + remove(node); + } else { + node.nodeValue = nodeValue.replace( + preserveNewLines ? /[\t ]+/g : /[\t\n\r ]+/g, + ' ' + ); + } + } + + node = nextNode; + } + } + + /** + * Extracts all the nodes between the start and end nodes + * + * @param {HTMLElement} startNode The node to start extracting at + * @param {HTMLElement} endNode The node to stop extracting at + * @return {DocumentFragment} + */ + function extractContents(startNode, endNode) { + var range = startNode.ownerDocument.createRange(); + + range.setStartBefore(startNode); + range.setEndAfter(endNode); + + return range.extractContents(); + } + + /** + * Gets the offset position of an element + * + * @param {HTMLElement} node + * @return {Object} An object with left and top properties + */ + function getOffset(node) { + var left = 0, + top = 0; + + while (node) { + left += node.offsetLeft; + top += node.offsetTop; + node = node.offsetParent; + } + + return { + left: left, + top: top + }; + } + + /** + * Gets the value of a CSS property from the elements style attribute + * + * @param {HTMLElement} elm + * @param {string} property + * @return {string} + */ + function getStyle(elm, property) { + var direction, styleValue, + elmStyle = elm.style; + + if (!cssPropertyNameCache[property]) { + cssPropertyNameCache[property] = camelCase(property); + } + + property = cssPropertyNameCache[property]; + styleValue = elmStyle[property]; + + // Add an exception for text-align + if ('textAlign' === property) { + direction = elmStyle.direction; + styleValue = styleValue || css(elm, property); + + if (css(elm.parentNode, property) === styleValue || + css(elm, 'display') !== 'block' || is(elm, 'hr,th')) { + return ''; + } + + // IE changes text-align to the same as the current direction + // so skip unless its not the same + if ((/right/i.test(styleValue) && direction === 'rtl') || + (/left/i.test(styleValue) && direction === 'ltr')) { + return ''; + } + } + + return styleValue; + } + + /** + * Tests if an element has a style. + * + * If values are specified it will check that the styles value + * matches one of the values + * + * @param {HTMLElement} elm + * @param {string} property + * @param {string|array} [values] + * @return {boolean} + */ + function hasStyle(elm, property, values) { + var styleValue = getStyle(elm, property); + + if (!styleValue) { + return false; + } + + return !values || styleValue === values || + (Array.isArray(values) && values.indexOf(styleValue) > -1); + } + + /** + * Default options for SCEditor + * @type {Object} + */ + var defaultOptions = { + /** @lends jQuery.sceditor.defaultOptions */ + /** + * Toolbar buttons order and groups. Should be comma separated and + * have a bar | to separate groups + * + * @type {string} + */ + toolbar: 'bold,italic,underline,strike,subscript,superscript|' + + 'left,center,right,justify|font,size,color,removeformat|' + + 'cut,copy,pastetext|bulletlist,orderedlist,indent,outdent|' + + 'table|code,quote|horizontalrule,image,email,link,unlink|' + + 'emoticon,youtube,date,time|ltr,rtl|print,maximize,source', + + /** + * Comma separated list of commands to excludes from the toolbar + * + * @type {string} + */ + toolbarExclude: null, + + /** + * Stylesheet to include in the WYSIWYG editor. This is what will style + * the WYSIWYG elements + * + * @type {string} + */ + style: 'jquery.sceditor.default.css', + + /** + * Comma separated list of fonts for the font selector + * + * @type {string} + */ + fonts: 'Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,' + + 'Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana', + + /** + * Colors should be comma separated and have a bar | to signal a new + * column. + * + * If null the colors will be auto generated. + * + * @type {string} + */ + colors: '#000000,#44B8FF,#1E92F7,#0074D9,#005DC2,#00369B,#b3d5f4|' + + '#444444,#C3FFFF,#9DF9FF,#7FDBFF,#68C4E8,#419DC1,#d9f4ff|' + + '#666666,#72FF84,#4CEA5E,#2ECC40,#17B529,#008E02,#c0f0c6|' + + '#888888,#FFFF44,#FFFA1E,#FFDC00,#E8C500,#C19E00,#fff5b3|' + + '#aaaaaa,#FFC95F,#FFA339,#FF851B,#E86E04,#C14700,#ffdbbb|' + + '#cccccc,#FF857A,#FF5F54,#FF4136,#E82A1F,#C10300,#ffc6c3|' + + '#eeeeee,#FF56FF,#FF30DC,#F012BE,#D900A7,#B20080,#fbb8ec|' + + '#ffffff,#F551FF,#CF2BE7,#B10DC9,#9A00B2,#9A00B2,#e8b6ef', + + /** + * The locale to use. + * @type {string} + */ + locale: attr(document.documentElement, 'lang') || 'en', + + /** + * The Charset to use + * @type {string} + */ + charset: 'utf-8', + + /** + * Compatibility mode for emoticons. + * + * Helps if you have emoticons such as :/ which would put an emoticon + * inside http:// + * + * This mode requires emoticons to be surrounded by whitespace or end of + * line chars. This mode has limited As You Type emoticon conversion + * support. It will not replace AYT for end of line chars, only + * emoticons surrounded by whitespace. They will still be replaced + * correctly when loaded just not AYT. + * + * @type {boolean} + */ + emoticonsCompat: false, + + /** + * If to enable emoticons. Can be changes at runtime using the + * emoticons() method. + * + * @type {boolean} + * @since 1.4.2 + */ + emoticonsEnabled: true, + + /** + * Emoticon root URL + * + * @type {string} + */ + emoticonsRoot: '', + emoticons: { + dropdown: { + ':)': 'emoticons/smile.png', + ':angel:': 'emoticons/angel.png', + ':angry:': 'emoticons/angry.png', + '8-)': 'emoticons/cool.png', + ':\'(': 'emoticons/cwy.png', + ':ermm:': 'emoticons/ermm.png', + ':D': 'emoticons/grin.png', + '<3': 'emoticons/heart.png', + ':(': 'emoticons/sad.png', + ':O': 'emoticons/shocked.png', + ':P': 'emoticons/tongue.png', + ';)': 'emoticons/wink.png' + }, + more: { + ':alien:': 'emoticons/alien.png', + ':blink:': 'emoticons/blink.png', + ':blush:': 'emoticons/blush.png', + ':cheerful:': 'emoticons/cheerful.png', + ':devil:': 'emoticons/devil.png', + ':dizzy:': 'emoticons/dizzy.png', + ':getlost:': 'emoticons/getlost.png', + ':happy:': 'emoticons/happy.png', + ':kissing:': 'emoticons/kissing.png', + ':ninja:': 'emoticons/ninja.png', + ':pinch:': 'emoticons/pinch.png', + ':pouty:': 'emoticons/pouty.png', + ':sick:': 'emoticons/sick.png', + ':sideways:': 'emoticons/sideways.png', + ':silly:': 'emoticons/silly.png', + ':sleeping:': 'emoticons/sleeping.png', + ':unsure:': 'emoticons/unsure.png', + ':woot:': 'emoticons/w00t.png', + ':wassat:': 'emoticons/wassat.png' + }, + hidden: { + ':whistling:': 'emoticons/whistling.png', + ':love:': 'emoticons/wub.png' + } + }, + + /** + * Width of the editor. Set to null for automatic with + * + * @type {?number} + */ + width: null, + + /** + * Height of the editor including toolbar. Set to null for automatic + * height + * + * @type {?number} + */ + height: null, + + /** + * If to allow the editor to be resized + * + * @type {boolean} + */ + resizeEnabled: true, + + /** + * Min resize to width, set to null for half textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinWidth: null, + /** + * Min resize to height, set to null for half textarea height or -1 for + * unlimited + * + * @type {?number} + */ + resizeMinHeight: null, + /** + * Max resize to height, set to null for double textarea height or -1 + * for unlimited + * + * @type {?number} + */ + resizeMaxHeight: null, + /** + * Max resize to width, set to null for double textarea width or -1 for + * unlimited + * + * @type {?number} + */ + resizeMaxWidth: null, + /** + * If resizing by height is enabled + * + * @type {boolean} + */ + resizeHeight: true, + /** + * If resizing by width is enabled + * + * @type {boolean} + */ + resizeWidth: true, + + /** + * Date format, will be overridden if locale specifies one. + * + * The words year, month and day will be replaced with the users current + * year, month and day. + * + * @type {string} + */ + dateFormat: 'year-month-day', + + /** + * Element to inset the toolbar into. + * + * @type {HTMLElement} + */ + toolbarContainer: null, + + /** + * If to enable paste filtering. This is currently experimental, please + * report any issues. + * + * @type {boolean} + */ + enablePasteFiltering: false, + + /** + * If to completely disable pasting into the editor + * + * @type {boolean} + */ + disablePasting: false, + + /** + * If the editor is read only. + * + * @type {boolean} + */ + readOnly: false, + + /** + * If to set the editor to right-to-left mode. + * + * If set to null the direction will be automatically detected. + * + * @type {boolean} + */ + rtl: false, + + /** + * If to auto focus the editor on page load + * + * @type {boolean} + */ + autofocus: false, + + /** + * If to auto focus the editor to the end of the content + * + * @type {boolean} + */ + autofocusEnd: true, + + /** + * If to auto expand the editor to fix the content + * + * @type {boolean} + */ + autoExpand: false, + + /** + * If to auto update original textbox on blur + * + * @type {boolean} + */ + autoUpdate: false, + + /** + * If to enable the browsers built in spell checker + * + * @type {boolean} + */ + spellcheck: true, + + /** + * If to run the source editor when there is no WYSIWYG support. Only + * really applies to mobile OS's. + * + * @type {boolean} + */ + runWithoutWysiwygSupport: false, + + /** + * If to load the editor in source mode and still allow switching + * between WYSIWYG and source mode + * + * @type {boolean} + */ + startInSourceMode: false, + + /** + * Optional ID to give the editor. + * + * @type {string} + */ + id: null, + + /** + * Comma separated list of plugins + * + * @type {string} + */ + plugins: '', + + /** + * z-index to set the editor container to. Needed for jQuery UI dialog. + * + * @type {?number} + */ + zIndex: null, + + /** + * If to trim the BBCode. Removes any spaces at the start and end of the + * BBCode string. + * + * @type {boolean} + */ + bbcodeTrim: false, + + /** + * If to disable removing block level elements by pressing backspace at + * the start of them + * + * @type {boolean} + */ + disableBlockRemove: false, + + /** + * BBCode parser options, only applies if using the editor in BBCode + * mode. + * + * See SCEditor.BBCodeParser.defaults for list of valid options + * + * @type {Object} + */ + parserOptions: { }, + + /** + * CSS that will be added to the to dropdown menu (eg. z-index) + * + * @type {Object} + */ + dropDownCss: { } + }; + + var USER_AGENT = navigator.userAgent; + + /** + * Detects the version of IE is being used if any. + * + * Will be the IE version number or undefined if the + * browser is not IE. + * + * Source: https://gist.github.com/527683 with extra code + * for IE 10 & 11 detection. + * + * @function + * @name ie + * @type {number} + */ + var ie = (function () { + var undef, + v = 3, + doc = document, + div = doc.createElement('div'), + all = div.getElementsByTagName('i'); + + do { + div.innerHTML = ''; + } while (all[0]); + + // Detect IE 10 as it doesn't support conditional comments. + if ((doc.documentMode && doc.all && window.atob)) { + v = 10; + } + + // Detect IE 11 + if (v === 4 && doc.documentMode) { + v = 11; + } + + return v > 4 ? v : undef; + }()); + + var edge = '-ms-ime-align' in document.documentElement.style; + + /** + * Detects if the browser is iOS + * + * Needed to fix iOS specific bugs + * + * @function + * @name ios + * @memberOf jQuery.sceditor + * @type {boolean} + */ + var ios = /iPhone|iPod|iPad| wosbrowser\//i.test(USER_AGENT); + + /** + * If the browser supports WYSIWYG editing (e.g. older mobile browsers). + * + * @function + * @name isWysiwygSupported + * @return {boolean} + */ + var isWysiwygSupported = (function () { + var match, isUnsupported; + + var div = document.createElement('div'); + div.contentEditable = true ; + + // Check if the contentEditable attribute is supported + if (!('contentEditable' in document.documentElement) || + div.contentEditable !== 'true') { + return false; + } + + // I think blackberry supports contentEditable or will at least + // give a valid value for the contentEditable detection above + // so it isn't included in the below tests. + + // I hate having to do UA sniffing but some mobile browsers say they + // support contentediable when it isn't usable, i.e. you can't enter + // text. + // This is the only way I can think of to detect them which is also how + // every other editor I've seen deals with this issue. + + // Exclude Opera mobile and mini + isUnsupported = /Opera Mobi|Opera Mini/i.test(USER_AGENT); + + if (/Android/i.test(USER_AGENT)) { + isUnsupported = true; + + if (/Safari/.test(USER_AGENT)) { + // Android browser 534+ supports content editable + // This also matches Chrome which supports content editable too + match = /Safari\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + } + + // The current version of Amazon Silk supports it, older versions didn't + // As it uses webkit like Android, assume it's the same and started + // working at versions >= 534 + if (/ Silk\//i.test(USER_AGENT)) { + match = /AppleWebKit\/(\d+)/.exec(USER_AGENT); + isUnsupported = (!match || !match[1] ? true : match[1] < 534); + } + + // iOS 5+ supports content editable + if (ios) { + // Block any version <= 4_x(_x) + isUnsupported = /OS [0-4](_\d)+ like Mac/i.test(USER_AGENT); + } + + // Firefox does support WYSIWYG on mobiles so override + // any previous value if using FF + if (/Firefox/i.test(USER_AGENT)) { + isUnsupported = false; + } + + if (/OneBrowser/i.test(USER_AGENT)) { + isUnsupported = false; + } + + // UCBrowser works but doesn't give a unique user agent + if (navigator.vendor === 'UCWEB') { + isUnsupported = false; + } + + // IE <= 9 is not supported any more + if (ie <= 9) { + isUnsupported = true; + } + + return !isUnsupported; + }()); + + // Must start with a valid scheme + // ^ + // Schemes that are considered safe + // (https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):| + // Relative schemes (//:) are considered safe + // (\\/\\/)| + // Image data URI's are considered safe + // data:image\\/(png|bmp|gif|p?jpe?g); + var VALID_SCHEME_REGEX = + /^(https?|s?ftp|mailto|spotify|skype|ssh|teamspeak|tel):|(\/\/)|data:image\/(png|bmp|gif|p?jpe?g);/i; + + /** + * Escapes a string so it's safe to use in regex + * + * @param {string} str + * @return {string} + */ + function regex(str) { + return str.replace(/([\-.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); + } + + /** + * Escapes all HTML entities in a string + * + * If noQuotes is set to false, all single and double + * quotes will also be escaped + * + * @param {string} str + * @param {boolean} [noQuotes=true] + * @return {string} + * @since 1.4.1 + */ + function entities(str, noQuotes) { + if (!str) { + return str; + } + + var replacements = { + '&': '&', + '<': '<', + '>': '>', + ' ': '  ', + '\r\n': '
    ', + '\r': '
    ', + '\n': '
    ' + }; + + if (noQuotes !== false) { + replacements['"'] = '"'; + replacements['\''] = '''; + replacements['`'] = '`'; + } + + str = str.replace(/ {2}|\r\n|[&<>\r\n'"`]/g, function (match) { + return replacements[match] || match; + }); + + return str; + } + + /** + * Escape URI scheme. + * + * Appends the current URL to a url if it has a scheme that is not: + * + * http + * https + * sftp + * ftp + * mailto + * spotify + * skype + * ssh + * teamspeak + * tel + * // + * data:image/(png|jpeg|jpg|pjpeg|bmp|gif); + * + * **IMPORTANT**: This does not escape any HTML in a url, for + * that use the escape.entities() method. + * + * @param {string} url + * @return {string} + * @since 1.4.5 + */ + function uriScheme(url) { + var path, + // If there is a : before a / then it has a scheme + hasScheme = /^[^\/]*:/i, + location = window.location; + + // Has no scheme or a valid scheme + if ((!url || !hasScheme.test(url)) || VALID_SCHEME_REGEX.test(url)) { + return url; + } + + path = location.pathname.split('/'); + path.pop(); + + return location.protocol + '//' + + location.host + + path.join('/') + '/' + + url; + } + + /** + * HTML templates used by the editor and default commands + * @type {Object} + * @private + */ + var _templates = { + html: + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '

    ' + + '', + + toolbarButton: '' + + '
    {dispName}
    ', + + emoticon: '', + + fontOpt: '{font}', + + sizeOpt: '{size}', + + pastetext: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + table: + '
    ' + + '
    ' + + '
    ', + + image: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + email: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + link: + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtubeMenu: + '
    ' + + '
    ' + + '
    ' + + '
    ', + + youtube: + '' + }; + + /** + * Replaces any params in a template with the passed params. + * + * If createHtml is passed it will return a DocumentFragment + * containing the parsed template. + * + * @param {string} name + * @param {Object} [params] + * @param {boolean} [createHtml] + * @returns {string|DocumentFragment} + * @private + */ + function _tmpl (name, params, createHtml) { + var template = _templates[name]; + + Object.keys(params).forEach(function (name) { + template = template.replace( + new RegExp(regex('{' + name + '}'), 'g'), params[name] + ); + }); + + if (createHtml) { + template = parseHTML(template); + } + + return template; + } + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX = ie && ie < 11; + + /** + * Fixes a bug in FF where it sometimes wraps + * new lines in their own list item. + * See issue #359 + */ + function fixFirefoxListBug(editor) { + // Only apply to Firefox as will break other browsers. + if ('mozHidden' in document) { + var node = editor.getBody(); + var next; + + while (node) { + next = node; + + if (next.firstChild) { + next = next.firstChild; + } else { + + while (next && !next.nextSibling) { + next = next.parentNode; + } + + if (next) { + next = next.nextSibling; + } + } + + if (node.nodeType === 3 && /[\n\r\t]+/.test(node.nodeValue)) { + // Only remove if newlines are collapsed + if (!/^pre/.test(css(node.parentNode, 'whiteSpace'))) { + remove(node); + } + } + + node = next; + } + } + } + + + /** + * Map of all the commands for SCEditor + * @type {Object} + * @name commands + * @memberOf jQuery.sceditor + */ + var defaultCmds = { + // START_COMMAND: Bold + bold: { + exec: 'bold', + tooltip: 'Bold', + shortcut: 'Ctrl+B' + }, + // END_COMMAND + // START_COMMAND: Italic + italic: { + exec: 'italic', + tooltip: 'Italic', + shortcut: 'Ctrl+I' + }, + // END_COMMAND + // START_COMMAND: Underline + underline: { + exec: 'underline', + tooltip: 'Underline', + shortcut: 'Ctrl+U' + }, + // END_COMMAND + // START_COMMAND: Strikethrough + strike: { + exec: 'strikethrough', + tooltip: 'Strikethrough' + }, + // END_COMMAND + // START_COMMAND: Subscript + subscript: { + exec: 'subscript', + tooltip: 'Subscript' + }, + // END_COMMAND + // START_COMMAND: Superscript + superscript: { + exec: 'superscript', + tooltip: 'Superscript' + }, + // END_COMMAND + + // START_COMMAND: Left + left: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'left' || align === (isLtr ? 'start' : 'end'); + } + }, + exec: 'justifyleft', + tooltip: 'Align left' + }, + // END_COMMAND + // START_COMMAND: Centre + center: { + exec: 'justifycenter', + tooltip: 'Center' + }, + // END_COMMAND + // START_COMMAND: Right + right: { + state: function (node) { + if (node && node.nodeType === 3) { + node = node.parentNode; + } + + if (node) { + var isLtr = css(node, 'direction') === 'ltr'; + var align = css(node, 'textAlign'); + + return align === 'right' || align === (isLtr ? 'end' : 'start'); + } + }, + exec: 'justifyright', + tooltip: 'Align right' + }, + // END_COMMAND + // START_COMMAND: Justify + justify: { + exec: 'justifyfull', + tooltip: 'Justify' + }, + // END_COMMAND + + // START_COMMAND: Font + font: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'font')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.opts.fonts.split(',').forEach(function (font) { + appendChild(content, _tmpl('fontOpt', { + font: font + }, true)); + }); + + editor.createDropDown(caller, 'font-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.font._dropDown(editor, caller, function (fontName) { + editor.execCommand('fontname', fontName); + }); + }, + tooltip: 'Font Name' + }, + // END_COMMAND + // START_COMMAND: Size + size: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'size')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + for (var i = 1; i <= 7; i++) { + appendChild(content, _tmpl('sizeOpt', { + size: i + }, true)); + } + + editor.createDropDown(caller, 'fontsize-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.size._dropDown(editor, caller, function (fontSize) { + editor.execCommand('fontsize', fontSize); + }); + }, + tooltip: 'Font Size' + }, + // END_COMMAND + // START_COMMAND: Colour + color: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'), + html = '', + cmd = defaultCmds.color; + + if (!cmd._htmlCache) { + editor.opts.colors.split('|').forEach(function (column) { + html += '
    '; + + column.split(',').forEach(function (color) { + html += + ''; + }); + + html += '
    '; + }); + + cmd._htmlCache = html; + } + + appendChild(content, parseHTML(cmd._htmlCache)); + + on(content, 'click', 'a', function (e) { + callback(data(this, 'color')); + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'color-picker', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.color._dropDown(editor, caller, function (color) { + editor.execCommand('forecolor', color); + }); + }, + tooltip: 'Font Color' + }, + // END_COMMAND + // START_COMMAND: Remove Format + removeformat: { + exec: 'removeformat', + tooltip: 'Remove Formatting' + }, + // END_COMMAND + + // START_COMMAND: Cut + cut: { + exec: 'cut', + tooltip: 'Cut', + errorMessage: 'Your browser does not allow the cut command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-X' + }, + // END_COMMAND + // START_COMMAND: Copy + copy: { + exec: 'copy', + tooltip: 'Copy', + errorMessage: 'Your browser does not allow the copy command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-C' + }, + // END_COMMAND + // START_COMMAND: Paste + paste: { + exec: 'paste', + tooltip: 'Paste', + errorMessage: 'Your browser does not allow the paste command. ' + + 'Please use the keyboard shortcut Ctrl/Cmd-V' + }, + // END_COMMAND + // START_COMMAND: Paste Text + pastetext: { + exec: function (caller) { + var val, + content = createElement('div'), + editor = this; + + appendChild(content, _tmpl('pastetext', { + label: editor._( + 'Paste your text inside the following box:' + ), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + val = find(content, '#txt')[0].value; + + if (val) { + editor.wysiwygEditorInsertText(val); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'pastetext', content); + }, + tooltip: 'Paste Text' + }, + // END_COMMAND + // START_COMMAND: Bullet List + bulletlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertunorderedlist'); + }, + tooltip: 'Bullet list' + }, + // END_COMMAND + // START_COMMAND: Ordered List + orderedlist: { + exec: function () { + fixFirefoxListBug(this); + this.execCommand('insertorderedlist'); + }, + tooltip: 'Numbered list' + }, + // END_COMMAND + // START_COMMAND: Indent + indent: { + state: function (parent$$1, firstBlock) { + // Only works with lists, for now + var range, startParent, endParent; + + if (is(firstBlock, 'li')) { + return 0; + } + + if (is(firstBlock, 'ul,ol,menu')) { + // if the whole list is selected, then this must be + // invalidated because the browser will place a + //
    there + range = this.getRangeHelper().selectedRange(); + + startParent = range.startContainer.parentNode; + endParent = range.endContainer.parentNode; + + // TODO: could use nodeType for this? + // Maybe just check the firstBlock contains both the start + //and end containers + + // Select the tag, not the textNode + // (that's why the parentNode) + if (startParent !== + startParent.parentNode.firstElementChild || + // work around a bug in FF + (is(endParent, 'li') && endParent !== + endParent.parentNode.lastElementChild)) { + return 0; + } + } + + return -1; + }, + exec: function () { + var editor = this, + block = editor.getRangeHelper().getFirstBlockParent(); + + editor.focus(); + + // An indent system is quite complicated as there are loads + // of complications and issues around how to indent text + // As default, let's just stay with indenting the lists, + // at least, for now. + if (closest(block, 'ul,ol,menu')) { + editor.execCommand('indent'); + } + }, + tooltip: 'Add indent' + }, + // END_COMMAND + // START_COMMAND: Outdent + outdent: { + state: function (parents$$1, firstBlock) { + return closest(firstBlock, 'ul,ol,menu') ? 0 : -1; + }, + exec: function () { + var block = this.getRangeHelper().getFirstBlockParent(); + if (closest(block, 'ul,ol,menu')) { + this.execCommand('outdent'); + } + }, + tooltip: 'Remove one indent' + }, + // END_COMMAND + + // START_COMMAND: Table + table: { + exec: function (caller) { + var editor = this, + content = createElement('div'); + + appendChild(content, _tmpl('table', { + rows: editor._('Rows:'), + cols: editor._('Cols:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var rows = Number(find(content, '#rows')[0].value), + cols = Number(find(content, '#cols')[0].value), + html = ''; + + if (rows > 0 && cols > 0) { + html += Array(rows + 1).join( + '' + + Array(cols + 1).join( + '' + ) + + '' + ); + + html += '
    ' + (IE_BR_FIX ? '' : '
    ') + '
    '; + + editor.wysiwygEditorInsertHtml(html); + editor.closeDropDown(true); + e.preventDefault(); + } + }); + + editor.createDropDown(caller, 'inserttable', content); + }, + tooltip: 'Insert a table' + }, + // END_COMMAND + + // START_COMMAND: Horizontal Rule + horizontalrule: { + exec: 'inserthorizontalrule', + tooltip: 'Insert a horizontal rule' + }, + // END_COMMAND + + // START_COMMAND: Code + code: { + exec: function () { + this.wysiwygEditorInsertHtml( + '', + (IE_BR_FIX ? '' : '
    ') + '
    ' + ); + }, + tooltip: 'Code' + }, + // END_COMMAND + + // START_COMMAND: Image + image: { + _dropDown: function (editor, caller, selected, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('image', { + url: editor._('URL:'), + width: editor._('Width (optional):'), + height: editor._('Height (optional):'), + insert: editor._('Insert') + }, true)); + + + var urlInput = find(content, '#image')[0]; + + urlInput.value = selected; + + on(content, 'click', '.button', function (e) { + if (urlInput.value) { + cb( + urlInput.value, + find(content, '#width')[0].value, + find(content, '#height')[0].value + ); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertimage', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.image._dropDown( + editor, + caller, + '', + function (url, width$$1, height$$1) { + var attrs = ''; + + if (width$$1) { + attrs += ' width="' + width$$1 + '"'; + } + + if (height$$1) { + attrs += ' height="' + height$$1 + '"'; + } + + editor.wysiwygEditorInsertHtml( + '' + ); + } + ); + }, + tooltip: 'Insert an image' + }, + // END_COMMAND + + // START_COMMAND: E-mail + email: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('email', { + label: editor._('E-mail:'), + desc: editor._('Description (optional):'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var email = find(content, '#email')[0].value; + + if (email) { + cb(email, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertemail', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.email._dropDown( + editor, + caller, + function (email, text) { + // needed for IE to reset the last range + editor.focus(); + + if (!editor.getRangeHelper().selectedHtml() || text) { + editor.wysiwygEditorInsertHtml( + '' + + (text || email) + + '' + ); + } else { + editor.execCommand('createlink', 'mailto:' + email); + } + } + ); + }, + tooltip: 'Insert an email' + }, + // END_COMMAND + + // START_COMMAND: Link + link: { + _dropDown: function (editor, caller, cb) { + var content = createElement('div'); + + appendChild(content, _tmpl('link', { + url: editor._('URL:'), + desc: editor._('Description (optional):'), + ins: editor._('Insert') + }, true)); + + var linkInput = find(content, '#link')[0]; + + function insertUrl(e) { + if (linkInput.value) { + cb(linkInput.value, find(content, '#des')[0].value); + } + + editor.closeDropDown(true); + e.preventDefault(); + } + + on(content, 'click', '.button', insertUrl); + on(content, 'keypress', function (e) { + // 13 = enter key + if (e.which === 13 && linkInput.value) { + insertUrl(e); + } + }, EVENT_CAPTURE); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (caller) { + var editor = this; + + defaultCmds.link._dropDown(editor, caller, function (url, text) { + // needed for IE to restore the last range + editor.focus(); + + // If there is no selected text then must set the URL as + // the text. Most browsers do this automatically, sadly + // IE doesn't. + if (text || !editor.getRangeHelper().selectedHtml()) { + text = text || url; + + editor.wysiwygEditorInsertHtml( + '' + text + '' + ); + } else { + editor.execCommand('createlink', url); + } + }); + }, + tooltip: 'Insert a link' + }, + // END_COMMAND + + // START_COMMAND: Unlink + unlink: { + state: function () { + return closest(this.currentNode(), 'a') ? 0 : -1; + }, + exec: function () { + var anchor = closest(this.currentNode(), 'a'); + + if (anchor) { + while (anchor.firstChild) { + insertBefore(anchor.firstChild, anchor); + } + + remove(anchor); + } + }, + tooltip: 'Unlink' + }, + // END_COMMAND + + + // START_COMMAND: Quote + quote: { + exec: function (caller, html, author) { + var before = '
    ', + end = '
    '; + + // if there is HTML passed set end to null so any selected + // text is replaced + if (html) { + author = (author ? '' + author + '' : ''); + before = before + author + html + end; + end = null; + // if not add a newline to the end of the inserted quote + } else if (this.getRangeHelper().selectedHtml() === '') { + end = (IE_BR_FIX ? '' : '
    ') + end; + } + + this.wysiwygEditorInsertHtml(before, end); + }, + tooltip: 'Insert a Quote' + }, + // END_COMMAND + + // START_COMMAND: Emoticons + emoticon: { + exec: function (caller) { + var editor = this; + + var createContent = function (includeMore) { + var moreLink, + opts = editor.opts, + emoticonsRoot = opts.emoticonsRoot || '', + emoticonsCompat = opts.emoticonsCompat, + rangeHelper = editor.getRangeHelper(), + startSpace = emoticonsCompat && + rangeHelper.getOuterText(true, 1) !== ' ' ? ' ' : '', + endSpace = emoticonsCompat && + rangeHelper.getOuterText(false, 1) !== ' ' ? ' ' : '', + content = createElement('div'), + line = createElement('div'), + perLine = 0, + emoticons = extend( + {}, + opts.emoticons.dropdown, + includeMore ? opts.emoticons.more : {} + ); + + appendChild(content, line); + + perLine = Math.sqrt(Object.keys(emoticons).length); + + on(content, 'click', 'img', function (e) { + editor.insert(startSpace + attr(this, 'alt') + endSpace, + null, false).closeDropDown(true); + + e.preventDefault(); + }); + + each(emoticons, function (code, emoticon) { + appendChild(line, createElement('img', { + src: emoticonsRoot + (emoticon.url || emoticon), + alt: code, + title: emoticon.tooltip || code + })); + + if (line.children.length >= perLine) { + line = createElement('div'); + appendChild(content, line); + } + }); + + if (!includeMore && opts.emoticons.more) { + moreLink = createElement('a', { + className: 'sceditor-more' + }); + + appendChild(moreLink, + document.createTextNode(editor._('More'))); + + on(moreLink, 'click', function (e) { + editor.createDropDown( + caller, 'more-emoticons', createContent(true) + ); + + e.preventDefault(); + }); + + appendChild(content, moreLink); + } + + return content; + }; + + editor.createDropDown(caller, 'emoticons', createContent(false)); + }, + txtExec: function (caller) { + defaultCmds.emoticon.exec.call(this, caller); + }, + tooltip: 'Insert an emoticon' + }, + // END_COMMAND + + // START_COMMAND: YouTube + youtube: { + _dropDown: function (editor, caller, callback) { + var content = createElement('div'); + + appendChild(content, _tmpl('youtubeMenu', { + label: editor._('Video URL:'), + insert: editor._('Insert') + }, true)); + + on(content, 'click', '.button', function (e) { + var val = find(content, '#link')[0].value; + var idMatch = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/); + var timeMatch = val.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/); + var time = 0; + + if (timeMatch) { + each(timeMatch[1].split(/[hms]/), function (i, val) { + if (val !== '') { + time = (time * 60) + Number(val); + } + }); + } + + if (idMatch && /^[a-zA-Z0-9_\-]{11}$/.test(idMatch[1])) { + callback(idMatch[1], time); + } + + editor.closeDropDown(true); + e.preventDefault(); + }); + + editor.createDropDown(caller, 'insertlink', content); + }, + exec: function (btn) { + var editor = this; + + defaultCmds.youtube._dropDown(editor, btn, function (id, time) { + editor.wysiwygEditorInsertHtml(_tmpl('youtube', { + id: id, + time: time + })); + }); + }, + tooltip: 'Insert a YouTube video' + }, + // END_COMMAND + + // START_COMMAND: Date + date: { + _date: function (editor) { + var now = new Date(), + year = now.getYear(), + month = now.getMonth() + 1, + day = now.getDate(); + + if (year < 2000) { + year = 1900 + year; + } + + if (month < 10) { + month = '0' + month; + } + + if (day < 10) { + day = '0' + day; + } + + return editor.opts.dateFormat + .replace(/year/i, year) + .replace(/month/i, month) + .replace(/day/i, day); + }, + exec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + txtExec: function () { + this.insertText(defaultCmds.date._date(this)); + }, + tooltip: 'Insert current date' + }, + // END_COMMAND + + // START_COMMAND: Time + time: { + _time: function () { + var now = new Date(), + hours = now.getHours(), + mins = now.getMinutes(), + secs = now.getSeconds(); + + if (hours < 10) { + hours = '0' + hours; + } + + if (mins < 10) { + mins = '0' + mins; + } + + if (secs < 10) { + secs = '0' + secs; + } + + return hours + ':' + mins + ':' + secs; + }, + exec: function () { + this.insertText(defaultCmds.time._time()); + }, + txtExec: function () { + this.insertText(defaultCmds.time._time()); + }, + tooltip: 'Insert current time' + }, + // END_COMMAND + + + // START_COMMAND: Ltr + ltr: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'ltr'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'ltr' ? '' : 'ltr'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Left-to-Right' + }, + // END_COMMAND + + // START_COMMAND: Rtl + rtl: { + state: function (parents$$1, firstBlock) { + return firstBlock && firstBlock.style.direction === 'rtl'; + }, + exec: function () { + var editor = this, + rangeHelper = editor.getRangeHelper(), + node = rangeHelper.getFirstBlockParent(); + + editor.focus(); + + if (!node || is(node, 'body')) { + editor.execCommand('formatBlock', 'p'); + + node = rangeHelper.getFirstBlockParent(); + + if (!node || is(node, 'body')) { + return; + } + } + + var toggleValue = css(node, 'direction') === 'rtl' ? '' : 'rtl'; + css(node, 'direction', toggleValue); + }, + tooltip: 'Right-to-Left' + }, + // END_COMMAND + + + // START_COMMAND: Print + print: { + exec: 'print', + tooltip: 'Print' + }, + // END_COMMAND + + // START_COMMAND: Maximize + maximize: { + state: function () { + return this.maximize(); + }, + exec: function () { + this.maximize(!this.maximize()); + }, + txtExec: function () { + this.maximize(!this.maximize()); + }, + tooltip: 'Maximize', + shortcut: 'Ctrl+Shift+M' + }, + // END_COMMAND + + // START_COMMAND: Source + source: { + state: function () { + return this.sourceMode(); + }, + exec: function () { + this.toggleSourceMode(); + }, + txtExec: function () { + this.toggleSourceMode(); + }, + tooltip: 'View source', + shortcut: 'Ctrl+Shift+S' + }, + // END_COMMAND + + // this is here so that commands above can be removed + // without having to remove the , after the last one. + // Needed for IE. + ignore: {} + }; + + var plugins = {}; + + /** + * Plugin Manager class + * @class PluginManager + * @name PluginManager + */ + function PluginManager(thisObj) { + /** + * Alias of this + * + * @private + * @type {Object} + */ + var base = this; + + /** + * Array of all currently registered plugins + * + * @type {Array} + * @private + */ + var registeredPlugins = []; + + + /** + * Changes a signals name from "name" into "signalName". + * + * @param {string} signal + * @return {string} + * @private + */ + var formatSignalName = function (signal) { + return 'signal' + signal.charAt(0).toUpperCase() + signal.slice(1); + }; + + /** + * Calls handlers for a signal + * + * @see call() + * @see callOnlyFirst() + * @param {Array} args + * @param {boolean} returnAtFirst + * @return {*} + * @private + */ + var callHandlers = function (args, returnAtFirst) { + args = [].slice.call(args); + + var idx, ret, + signal = formatSignalName(args.shift()); + + for (idx = 0; idx < registeredPlugins.length; idx++) { + if (signal in registeredPlugins[idx]) { + ret = registeredPlugins[idx][signal].apply(thisObj, args); + + if (returnAtFirst) { + return ret; + } + } + } + }; + + /** + * Calls all handlers for the passed signal + * + * @param {string} signal + * @param {...string} args + * @function + * @name call + * @memberOf PluginManager.prototype + */ + base.call = function () { + callHandlers(arguments, false); + }; + + /** + * Calls the first handler for a signal, and returns the + * + * @param {string} signal + * @param {...string} args + * @return {*} The result of calling the handler + * @function + * @name callOnlyFirst + * @memberOf PluginManager.prototype + */ + base.callOnlyFirst = function () { + return callHandlers(arguments, true); + }; + + /** + * Checks if a signal has a handler + * + * @param {string} signal + * @return {boolean} + * @function + * @name hasHandler + * @memberOf PluginManager.prototype + */ + base.hasHandler = function (signal) { + var i = registeredPlugins.length; + signal = formatSignalName(signal); + + while (i--) { + if (signal in registeredPlugins[i]) { + return true; + } + } + + return false; + }; + + /** + * Checks if the plugin exists in plugins + * + * @param {string} plugin + * @return {boolean} + * @function + * @name exists + * @memberOf PluginManager.prototype + */ + base.exists = function (plugin) { + if (plugin in plugins) { + plugin = plugins[plugin]; + + return typeof plugin === 'function' && + typeof plugin.prototype === 'object'; + } + + return false; + }; + + /** + * Checks if the passed plugin is currently registered. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name isRegistered + * @memberOf PluginManager.prototype + */ + base.isRegistered = function (plugin) { + if (base.exists(plugin)) { + var idx = registeredPlugins.length; + + while (idx--) { + if (registeredPlugins[idx] instanceof plugins[plugin]) { + return true; + } + } + } + + return false; + }; + + /** + * Registers a plugin to receive signals + * + * @param {string} plugin + * @return {boolean} + * @function + * @name register + * @memberOf PluginManager.prototype + */ + base.register = function (plugin) { + if (!base.exists(plugin) || base.isRegistered(plugin)) { + return false; + } + + plugin = new plugins[plugin](); + registeredPlugins.push(plugin); + + if ('init' in plugin) { + plugin.init.call(thisObj); + } + + return true; + }; + + /** + * Deregisters a plugin. + * + * @param {string} plugin + * @return {boolean} + * @function + * @name deregister + * @memberOf PluginManager.prototype + */ + base.deregister = function (plugin) { + var removedPlugin, + pluginIdx = registeredPlugins.length, + removed = false; + + if (!base.isRegistered(plugin)) { + return removed; + } + + while (pluginIdx--) { + if (registeredPlugins[pluginIdx] instanceof plugins[plugin]) { + removedPlugin = registeredPlugins.splice(pluginIdx, 1)[0]; + removed = true; + + if ('destroy' in removedPlugin) { + removedPlugin.destroy.call(thisObj); + } + } + } + + return removed; + }; + + /** + * Clears all plugins and removes the owner reference. + * + * Calling any functions on this object after calling + * destroy will cause a JS error. + * + * @name destroy + * @memberOf PluginManager.prototype + */ + base.destroy = function () { + var i = registeredPlugins.length; + + while (i--) { + if ('destroy' in registeredPlugins[i]) { + registeredPlugins[i].destroy.call(thisObj); + } + } + + registeredPlugins = []; + thisObj = null; + }; + } + + PluginManager.plugins = plugins; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$1 = ie && ie < 11; + + + /** + * Gets the text, start/end node and offset for + * length chars left or right of the passed node + * at the specified offset. + * + * @param {Node} node + * @param {number} offset + * @param {boolean} isLeft + * @param {number} length + * @return {Object} + * @private + */ + var outerText = function (range, isLeft, length) { + var nodeValue, remaining, start, end, node, + text = '', + next = range.startContainer, + offset = range.startOffset; + + // Handle cases where node is a paragraph and offset + // refers to the index of a text node. + // 3 = text node + if (next && next.nodeType !== 3) { + next = next.childNodes[offset]; + offset = 0; + } + + start = end = offset; + + while (length > text.length && next && next.nodeType === 3) { + nodeValue = next.nodeValue; + remaining = length - text.length; + + // If not the first node, start and end should be at their + // max values as will be updated when getting the text + if (node) { + end = nodeValue.length; + start = 0; + } + + node = next; + + if (isLeft) { + start = Math.max(end - remaining, 0); + offset = start; + + text = nodeValue.substr(start, end - start) + text; + next = node.previousSibling; + } else { + end = Math.min(remaining, nodeValue.length); + offset = start + end; + + text += nodeValue.substr(start, end); + next = node.nextSibling; + } + } + + return { + node: node || next, + offset: offset, + text: text + }; + }; + + /** + * Range helper + * + * @class RangeHelper + * @name RangeHelper + */ + function RangeHelper(win, d) { + var _createMarker, _prepareInput, + doc = d || win.contentDocument || win.document, + startMarker = 'sceditor-start-marker', + endMarker = 'sceditor-end-marker', + base = this; + + /** + * Inserts HTML into the current range replacing any selected + * text. + * + * If endHTML is specified the selected contents will be put between + * html and endHTML. If there is nothing selected html and endHTML are + * just concatenate together. + * + * @param {string} html + * @param {string} [endHTML] + * @return False on fail + * @function + * @name insertHTML + * @memberOf RangeHelper.prototype + */ + base.insertHTML = function (html, endHTML) { + var node, div, + range = base.selectedRange(); + + if (!range) { + return false; + } + + if (endHTML) { + html += base.selectedHtml() + endHTML; + } + + div = createElement('p', {}, doc); + node = doc.createDocumentFragment(); + div.innerHTML = html; + + while (div.firstChild) { + appendChild(node, div.firstChild); + } + + base.insertNode(node); + }; + + /** + * Prepares HTML to be inserted by adding a zero width space + * if the last child is empty and adding the range start/end + * markers to the last child. + * + * @param {Node|string} node + * @param {Node|string} [endNode] + * @param {boolean} [returnHtml] + * @return {Node|string} + * @private + */ + _prepareInput = function (node, endNode, returnHtml) { + var lastChild, + frag = doc.createDocumentFragment(); + + if (typeof node === 'string') { + if (endNode) { + node += base.selectedHtml() + endNode; + } + + frag = parseHTML(node); + } else { + appendChild(frag, node); + + if (endNode) { + appendChild(frag, base.selectedRange().extractContents()); + appendChild(frag, endNode); + } + } + + if (!(lastChild = frag.lastChild)) { + return; + } + + while (!isInline(lastChild.lastChild, true)) { + lastChild = lastChild.lastChild; + } + + if (canHaveChildren(lastChild)) { + // Webkit won't allow the cursor to be placed inside an + // empty tag, so add a zero width space to it. + if (!lastChild.lastChild) { + appendChild(lastChild, document.createTextNode('\u200B')); + } + } else { + lastChild = frag; + } + + base.removeMarkers(); + + // Append marks to last child so when restored cursor will be in + // the right place + appendChild(lastChild, _createMarker(startMarker)); + appendChild(lastChild, _createMarker(endMarker)); + + if (returnHtml) { + var div = createElement('div'); + appendChild(div, frag); + + return div.innerHTML; + } + + return frag; + }; + + /** + * The same as insertHTML except with DOM nodes instead + * + * Warning: the nodes must belong to the + * document they are being inserted into. Some browsers + * will throw exceptions if they don't. + * + * Returns boolean false on fail + * + * @param {Node} node + * @param {Node} endNode + * @return {false|undefined} + * @function + * @name insertNode + * @memberOf RangeHelper.prototype + */ + base.insertNode = function (node, endNode) { + var input = _prepareInput(node, endNode), + range = base.selectedRange(), + parent$$1 = range.commonAncestorContainer; + + if (!input) { + return false; + } + + range.deleteContents(); + + // FF allows
    to be selected but inserting a node + // into
    will cause it not to be displayed so must + // insert before the
    in FF. + // 3 = TextNode + if (parent$$1 && parent$$1.nodeType !== 3 && !canHaveChildren(parent$$1)) { + insertBefore(input, parent$$1); + } else { + range.insertNode(input); + } + + base.restoreRange(); + }; + + /** + * Clones the selected Range + * + * @return {Range} + * @function + * @name cloneSelected + * @memberOf RangeHelper.prototype + */ + base.cloneSelected = function () { + var range = base.selectedRange(); + + if (range) { + return range.cloneRange(); + } + }; + + /** + * Gets the selected Range + * + * @return {Range} + * @function + * @name selectedRange + * @memberOf RangeHelper.prototype + */ + base.selectedRange = function () { + var range, firstChild, + sel = win.getSelection(); + + if (!sel) { + return; + } + + // When creating a new range, set the start to the first child + // element of the body element to avoid errors in FF. + if (sel.rangeCount <= 0) { + firstChild = doc.body; + while (firstChild.firstChild) { + firstChild = firstChild.firstChild; + } + + range = doc.createRange(); + // Must be setStartBefore otherwise it can cause infinite + // loops with lists in WebKit. See issue 442 + range.setStartBefore(firstChild); + + sel.addRange(range); + } + + if (sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } + + return range; + }; + + /** + * Gets if there is currently a selection + * + * @return {boolean} + * @function + * @name hasSelection + * @since 1.4.4 + * @memberOf RangeHelper.prototype + */ + base.hasSelection = function () { + var sel = win.getSelection(); + + return sel && sel.rangeCount > 0; + }; + + /** + * Gets the currently selected HTML + * + * @return {string} + * @function + * @name selectedHtml + * @memberOf RangeHelper.prototype + */ + base.selectedHtml = function () { + var div, + range = base.selectedRange(); + + if (range) { + div = createElement('p', {}, doc); + appendChild(div, range.cloneContents()); + + return div.innerHTML; + } + + return ''; + }; + + /** + * Gets the parent node of the selected contents in the range + * + * @return {HTMLElement} + * @function + * @name parentNode + * @memberOf RangeHelper.prototype + */ + base.parentNode = function () { + var range = base.selectedRange(); + + if (range) { + return range.commonAncestorContainer; + } + }; + + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @return {HTMLElement} + * @function + * @name getFirstBlockParent + * @memberOf RangeHelper.prototype + */ + /** + * Gets the first block level parent of the selected + * contents of the range. + * + * @param {Node} [n] The element to get the first block level parent from + * @return {HTMLElement} + * @function + * @name getFirstBlockParent^2 + * @since 1.4.1 + * @memberOf RangeHelper.prototype + */ + base.getFirstBlockParent = function (node) { + var func = function (elm) { + if (!isInline(elm, true)) { + return elm; + } + + elm = elm ? elm.parentNode : null; + + return elm ? func(elm) : elm; + }; + + return func(node || base.parentNode()); + }; + + /** + * Inserts a node at either the start or end of the current selection + * + * @param {Bool} start + * @param {Node} node + * @function + * @name insertNodeAt + * @memberOf RangeHelper.prototype + */ + base.insertNodeAt = function (start, node) { + var currentRange = base.selectedRange(), + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(start); + range.insertNode(node); + + // Reselect the current range. + // Fixes issue with Chrome losing the selection. Issue#82 + base.selectRange(currentRange); + }; + + /** + * Creates a marker node + * + * @param {string} id + * @return {HTMLSpanElement} + * @private + */ + _createMarker = function (id) { + base.removeMarker(id); + + var marker = createElement('span', { + id: id, + className: 'sceditor-selection sceditor-ignore', + style: 'display:none;line-height:0' + }, doc); + + marker.innerHTML = ' '; + + return marker; + }; + + /** + * Inserts start/end markers for the current selection + * which can be used by restoreRange to re-select the + * range. + * + * @memberOf RangeHelper.prototype + * @function + * @name insertMarkers + */ + base.insertMarkers = function () { + var currentRange = base.selectedRange(); + var startNode = _createMarker(startMarker); + + base.removeMarkers(); + base.insertNodeAt(true, startNode); + + // Fixes issue with end marker sometimes being placed before + // the start marker when the range is collapsed. + if (currentRange && currentRange.collapsed) { + startNode.parentNode.insertBefore( + _createMarker(endMarker), startNode.nextSibling); + } else { + base.insertNodeAt(false, _createMarker(endMarker)); + } + }; + + /** + * Gets the marker with the specified ID + * + * @param {string} id + * @return {Node} + * @function + * @name getMarker + * @memberOf RangeHelper.prototype + */ + base.getMarker = function (id) { + return doc.getElementById(id); + }; + + /** + * Removes the marker with the specified ID + * + * @param {string} id + * @function + * @name removeMarker + * @memberOf RangeHelper.prototype + */ + base.removeMarker = function (id) { + var marker = base.getMarker(id); + + if (marker) { + remove(marker); + } + }; + + /** + * Removes the start/end markers + * + * @function + * @name removeMarkers + * @memberOf RangeHelper.prototype + */ + base.removeMarkers = function () { + base.removeMarker(startMarker); + base.removeMarker(endMarker); + }; + + /** + * Saves the current range location. Alias of insertMarkers() + * + * @function + * @name saveRage + * @memberOf RangeHelper.prototype + */ + base.saveRange = function () { + base.insertMarkers(); + }; + + /** + * Select the specified range + * + * @param {Range} range + * @function + * @name selectRange + * @memberOf RangeHelper.prototype + */ + base.selectRange = function (range) { + var lastChild; + var sel = win.getSelection(); + var container = range.endContainer; + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$1 && range.collapsed && container && + !isInline(container, true)) { + + lastChild = container.lastChild; + while (lastChild && is(lastChild, '.sceditor-ignore')) { + lastChild = lastChild.previousSibling; + } + + if (is(lastChild, 'br')) { + var rng = doc.createRange(); + rng.setEndAfter(lastChild); + rng.collapse(false); + + if (base.compare(range, rng)) { + range.setStartBefore(lastChild); + range.collapse(true); + } + } + } + + if (sel) { + base.clear(); + sel.addRange(range); + } + }; + + /** + * Restores the last range saved by saveRange() or insertMarkers() + * + * @function + * @name restoreRange + * @memberOf RangeHelper.prototype + */ + base.restoreRange = function () { + var isCollapsed, + range = base.selectedRange(), + start = base.getMarker(startMarker), + end = base.getMarker(endMarker); + + if (!start || !end || !range) { + return false; + } + + isCollapsed = start.nextSibling === end; + + range = doc.createRange(); + range.setStartBefore(start); + range.setEndAfter(end); + + if (isCollapsed) { + range.collapse(true); + } + + base.selectRange(range); + base.removeMarkers(); + }; + + /** + * Selects the text left and right of the current selection + * + * @param {number} left + * @param {number} right + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.selectOuterText = function (left, right) { + var start, end, + range = base.cloneSelected(); + + if (!range) { + return false; + } + + range.collapse(false); + + start = outerText(range, true, left); + end = outerText(range, false, right); + + range.setStart(start.node, start.offset); + range.setEnd(end.node, end.offset); + + base.selectRange(range); + }; + + /** + * Gets the text left or right of the current selection + * + * @param {boolean} before + * @param {number} length + * @return {string} + * @since 1.4.3 + * @function + * @name selectOuterText + * @memberOf RangeHelper.prototype + */ + base.getOuterText = function (before, length) { + var range = base.cloneSelected(); + + if (!range) { + return ''; + } + + range.collapse(!before); + + return outerText(range, before, length).text; + }; + + /** + * Replaces keywords with values based on the current caret position + * + * @param {Array} keywords + * @param {boolean} includeAfter If to include the text after the + * current caret position or just + * text before + * @param {boolean} keywordsSorted If the keywords array is pre + * sorted shortest to longest + * @param {number} longestKeyword Length of the longest keyword + * @param {boolean} requireWhitespace If the key must be surrounded + * by whitespace + * @param {string} keypressChar If this is being called from + * a keypress event, this should be + * set to the pressed character + * @return {boolean} + * @function + * @name replaceKeyword + * @memberOf RangeHelper.prototype + */ + // eslint-disable-next-line max-params + base.replaceKeyword = function ( + keywords, + includeAfter, + keywordsSorted, + longestKeyword, + requireWhitespace, + keypressChar + ) { + if (!keywordsSorted) { + keywords.sort(function (a, b) { + return a[0].length - b[0].length; + }); + } + + var outerText, match, matchPos, startIndex, + leftLen, charsLeft, keyword, keywordLen, + whitespaceRegex = '(^|[\\s\xA0\u2002\u2003\u2009])', + keywordIdx = keywords.length, + whitespaceLen = requireWhitespace ? 1 : 0, + maxKeyLen = longestKeyword || + keywords[keywordIdx - 1][0].length; + + if (requireWhitespace) { + maxKeyLen++; + } + + keypressChar = keypressChar || ''; + outerText = base.getOuterText(true, maxKeyLen); + leftLen = outerText.length; + outerText += keypressChar; + + if (includeAfter) { + outerText += base.getOuterText(false, maxKeyLen); + } + + while (keywordIdx--) { + keyword = keywords[keywordIdx][0]; + keywordLen = keyword.length; + startIndex = Math.max(0, leftLen - keywordLen - whitespaceLen); + matchPos = -1; + + if (requireWhitespace) { + match = outerText + .substr(startIndex) + .match(new RegExp(whitespaceRegex + + regex(keyword) + whitespaceRegex)); + + if (match) { + // Add the length of the text that was removed by + // substr() and also add 1 for the whitespace + matchPos = match.index + startIndex + match[1].length; + } + } else { + matchPos = outerText.indexOf(keyword, startIndex); + } + + if (matchPos > -1) { + // Make sure the match is between before and + // after, not just entirely in one side or the other + if (matchPos <= leftLen && + matchPos + keywordLen + whitespaceLen >= leftLen) { + charsLeft = leftLen - matchPos; + + // If the keypress char is white space then it should + // not be replaced, only chars that are part of the + // key should be replaced. + base.selectOuterText( + charsLeft, + keywordLen - charsLeft - + (/^\S/.test(keypressChar) ? 1 : 0) + ); + + base.insertHTML(keywords[keywordIdx][1]); + return true; + } + } + } + + return false; + }; + + /** + * Compares two ranges. + * + * If rangeB is undefined it will be set to + * the current selected range + * + * @param {Range} rngA + * @param {Range} [rngB] + * @return {boolean} + * @function + * @name compare + * @memberOf RangeHelper.prototype + */ + base.compare = function (rngA, rngB) { + if (!rngB) { + rngB = base.selectedRange(); + } + + if (!rngA || !rngB) { + return !rngA && !rngB; + } + + return rngA.compareBoundaryPoints(Range.END_TO_END, rngB) === 0 && + rngA.compareBoundaryPoints(Range.START_TO_START, rngB) === 0; + }; + + /** + * Removes any current selection + * + * @since 1.4.6 + * @function + * @name clear + * @memberOf RangeHelper.prototype + */ + base.clear = function () { + var sel = win.getSelection(); + + if (sel) { + if (sel.removeAllRanges) { + sel.removeAllRanges(); + } else if (sel.empty) { + sel.empty(); + } + } + }; + } + + /** + * Checks all emoticons are surrounded by whitespace and + * replaces any that aren't with with their emoticon code. + * + * @param {HTMLElement} node + * @param {rangeHelper} rangeHelper + * @return {void} + */ + function checkWhitespace(node, rangeHelper) { + var noneWsRegex = /[^\s\xA0\u2002\u2003\u2009\u00a0]+/; + var emoticons = node && find(node, 'img[data-sceditor-emoticon]'); + + if (!node || !emoticons.length) { + return; + } + + for (var i = 0; i < emoticons.length; i++) { + var emoticon = emoticons[i]; + var parent$$1 = emoticon.parentNode; + var prev = emoticon.previousSibling; + var next = emoticon.nextSibling; + + if ((!prev || !noneWsRegex.test(prev.nodeValue.slice(-1))) && + (!next || !noneWsRegex.test((next.nodeValue || '')[0]))) { + continue; + } + + var range = rangeHelper.cloneSelected(); + var rangeStart = -1; + var rangeStartContainer = range.startContainer; + var previousText = prev.nodeValue; + + // For IE's HTMLPhraseElement + if (previousText === null) { + previousText = prev.innerText || ''; + } + + previousText += data(emoticon, 'sceditor-emoticon'); + + // If the cursor is after the removed emoticon, add + // the length of the newly added text to it + if (rangeStartContainer === next) { + rangeStart = previousText.length + range.startOffset; + } + + // If the cursor is set before the next node, set it to + // the end of the new text node + if (rangeStartContainer === node && + node.childNodes[range.startOffset] === next) { + rangeStart = previousText.length; + } + + // If the cursor is set before the removed emoticon, + // just keep it at that position + if (rangeStartContainer === prev) { + rangeStart = range.startOffset; + } + + if (!next || next.nodeType !== TEXT_NODE) { + next = parent$$1.insertBefore( + parent$$1.ownerDocument.createTextNode(''), next + ); + } + + next.insertData(0, previousText); + remove(prev); + remove(emoticon); + + // Need to update the range starting position if it's been modified + if (rangeStart > -1) { + range.setStart(next, rangeStart); + range.collapse(true); + rangeHelper.selectRange(range); + } + } + } + + /** + * Replaces any emoticons inside the root node with images. + * + * emoticons should be an object where the key is the emoticon + * code and the value is the HTML to replace it with. + * + * @param {HTMLElement} root + * @param {Object} emoticons + * @param {boolean} emoticonsCompat + * @return {void} + */ + function replace(root, emoticons, emoticonsCompat) { + var doc = root.ownerDocument; + var space = '(^|\\s|\xA0|\u2002|\u2003|\u2009|$)'; + var emoticonCodes = []; + var emoticonRegex = {}; + + // TODO: Make this tag configurable. + if (parent(root, 'code')) { + return; + } + + each(emoticons, function (key) { + emoticonRegex[key] = new RegExp(space + regex(key) + space); + emoticonCodes.push(key); + }); + + // Sort keys longest to shortest so that longer keys + // take precedence (avoids bugs with shorter keys partially + // matching longer ones) + emoticonCodes.sort(function (a, b) { + return b.length - a.length; + }); + + (function convert(node) { + node = node.firstChild; + + while (node) { + // TODO: Make this tag configurable. + if (node.nodeType === ELEMENT_NODE && !is(node, 'code')) { + convert(node); + } + + if (node.nodeType === TEXT_NODE) { + for (var i = 0; i < emoticonCodes.length; i++) { + var text = node.nodeValue; + var key = emoticonCodes[i]; + var index = emoticonsCompat ? + text.search(emoticonRegex[key]) : + text.indexOf(key); + + if (index > -1) { + // When emoticonsCompat is enabled this will be the + // position after any white space + var startIndex = text.indexOf(key, index); + var fragment = parseHTML(emoticons[key], doc); + var after = text.substr(startIndex + key.length); + + fragment.appendChild(doc.createTextNode(after)); + + node.nodeValue = text.substr(0, startIndex); + node.parentNode + .insertBefore(fragment, node.nextSibling); + } + } + } + + node = node.nextSibling; + } + }(root)); + } + + var globalWin = window; + var globalDoc = document; + + var IE_VER = ie; + + // In IE < 11 a BR at the end of a block level element + // causes a line break. In all other browsers it's collapsed. + var IE_BR_FIX$2 = IE_VER && IE_VER < 11; + + var IMAGE_MIME_REGEX = /^image\/(p?jpe?g|gif|png|bmp)$/i; + + /** + * Wrap inlines that are in the root in paragraphs. + * + * @param {HTMLBodyElement} body + * @param {Document} doc + * @private + */ + function wrapInlines(body, doc) { + var wrapper; + + traverse(body, function (node) { + if (isInline(node, true)) { + if (!wrapper) { + wrapper = createElement('p', {}, doc); + insertBefore(wrapper, node); + } + + if (node.nodeType !== TEXT_NODE || node.nodeValue !== '') { + appendChild(wrapper, node); + } + } else { + wrapper = null; + } + }, false, true); + } + + /** + * SCEditor - A lightweight WYSIWYG editor + * + * @param {HTMLTextAreaElement} original The textarea to be converted + * @param {Object} userOptions + * @class SCEditor + * @name SCEditor + */ + function SCEditor(original, userOptions) { + /** + * Alias of this + * + * @private + */ + var base = this; + + /** + * Editor format like BBCode or HTML + */ + var format; + + /** + * The div which contains the editor and toolbar + * + * @type {HTMLDivElement} + * @private + */ + var editorContainer; + + /** + * Map of events handlers bound to this instance. + * + * @type {Object} + * @private + */ + var eventHandlers = {}; + + /** + * The editors toolbar + * + * @type {HTMLDivElement} + * @private + */ + var toolbar; + + /** + * The editors iframe which should be in design mode + * + * @type {HTMLIFrameElement} + * @private + */ + var wysiwygEditor; + + /** + * The editors window + * + * @type {Window} + * @private + */ + var wysiwygWindow; + + /** + * The WYSIWYG editors body element + * + * @type {HTMLBodyElement} + * @private + */ + var wysiwygBody; + + /** + * The WYSIWYG editors document + * + * @type {Document} + * @private + */ + var wysiwygDocument; + + /** + * The editors textarea for viewing source + * + * @type {HTMLTextAreaElement} + * @private + */ + var sourceEditor; + + /** + * The current dropdown + * + * @type {HTMLDivElement} + * @private + */ + var dropdown; + + /** + * Store the last cursor position. Needed for IE because it forgets + * + * @type {Range} + * @private + */ + var lastRange; + + /** + * If the user is currently composing text via IME + * @type {boolean} + */ + var isComposing; + + /** + * Timer for valueChanged key handler + * @type {number} + */ + var valueChangedKeyUpTimer; + + /** + * The editors locale + * + * @private + */ + var locale; + + /** + * Stores a cache of preloaded images + * + * @private + * @type {Array.} + */ + var preLoadCache = []; + + /** + * The editors rangeHelper instance + * + * @type {RangeHelper} + * @private + */ + var rangeHelper; + + /** + * An array of button state handlers + * + * @type {Array.} + * @private + */ + var btnStateHandlers = []; + + /** + * Plugin manager instance + * + * @type {PluginManager} + * @private + */ + var pluginManager; + + /** + * The current node containing the selection/caret + * + * @type {Node} + * @private + */ + var currentNode; + + /** + * The first block level parent of the current node + * + * @type {node} + * @private + */ + var currentBlockNode; + + /** + * The current node selection/caret + * + * @type {Object} + * @private + */ + var currentSelection; + + /** + * Used to make sure only 1 selection changed + * check is called every 100ms. + * + * Helps improve performance as it is checked a lot. + * + * @type {boolean} + * @private + */ + var isSelectionCheckPending; + + /** + * If content is required (equivalent to the HTML5 required attribute) + * + * @type {boolean} + * @private + */ + var isRequired; + + /** + * The inline CSS style element. Will be undefined + * until css() is called for the first time. + * + * @type {HTMLStyleElement} + * @private + */ + var inlineCss; + + /** + * Object containing a list of shortcut handlers + * + * @type {Object} + * @private + */ + var shortcutHandlers = {}; + + /** + * The min and max heights that autoExpand should stay within + * + * @type {Object} + * @private + */ + var autoExpandBounds; + + /** + * Timeout for the autoExpand function to throttle calls + * + * @private + */ + var autoExpandThrottle; + + /** + * Cache of the current toolbar buttons + * + * @type {Object} + * @private + */ + var toolbarButtons = {}; + + /** + * Last scroll position before maximizing so + * it can be restored when finished. + * + * @type {number} + * @private + */ + var maximizeScrollPosition; + + /** + * Stores the contents while a paste is taking place. + * + * Needed to support browsers that lack clipboard API support. + * + * @type {?DocumentFragment} + * @private + */ + var pasteContentFragment; + + /** + * All the emoticons from dropdown, more and hidden combined + * and with the emoticons root set + * + * @type {!Object} + * @private + */ + var allEmoticons = {}; + + /** + * Current icon set if any + * + * @type {?Object} + * @private + */ + var icons; + + /** + * Private functions + * @private + */ + var init, + replaceEmoticons, + handleCommand, + saveRange, + initEditor, + initPlugins, + initLocale, + initToolBar, + initOptions, + initEvents, + initResize, + initEmoticons, + handlePasteEvt, + handlePasteData, + handleKeyDown, + handleBackSpace, + handleKeyPress, + handleFormReset, + handleMouseDown, + handleComposition, + handleEvent, + handleDocumentClick, + updateToolBar, + updateActiveButtons, + sourceEditorSelectedText, + appendNewLine, + checkSelectionChanged, + checkNodeChanged, + autofocus, + emoticonsKeyPress, + emoticonsCheckWhitespace, + currentStyledBlockNode, + triggerValueChanged, + valueChangedBlur, + valueChangedKeyUp, + autoUpdate, + autoExpand; + + /** + * All the commands supported by the editor + * @name commands + * @memberOf SCEditor.prototype + */ + base.commands = extend(true, {}, (userOptions.commands || defaultCmds)); + + /** + * Options for this editor instance + * @name opts + * @memberOf SCEditor.prototype + */ + var options = base.opts = extend( + true, {}, defaultOptions, userOptions + ); + + // Don't deep extend emoticons (fixes #565) + base.opts.emoticons = userOptions.emoticons || defaultOptions.emoticons; + + /** + * Creates the editor iframe and textarea + * @private + */ + init = function () { + original._sceditor = base; + + // Load locale + if (options.locale && options.locale !== 'en') { + initLocale(); + } + + editorContainer = createElement('div', { + className: 'sceditor-container' + }); + + insertBefore(editorContainer, original); + css(editorContainer, 'z-index', options.zIndex); + + // Add IE version to the container to allow IE specific CSS + // fixes without using CSS hacks or conditional comments + if (IE_VER) { + addClass(editorContainer, 'ie ie' + IE_VER); + } + + isRequired = original.required; + original.required = false; + + var FormatCtor = SCEditor.formats[options.format]; + format = FormatCtor ? new FormatCtor() : {}; + if ('init' in format) { + format.init.call(base); + } + + // create the editor + initPlugins(); + initEmoticons(); + initToolBar(); + initEditor(); + initOptions(); + initEvents(); + + // force into source mode if is a browser that can't handle + // full editing + if (!isWysiwygSupported) { + base.toggleSourceMode(); + } + + updateActiveButtons(); + + var loaded = function () { + off(globalWin, 'load', loaded); + + if (options.autofocus) { + autofocus(); + } + + autoExpand(); + appendNewLine(); + // TODO: use editor doc and window? + pluginManager.call('ready'); + if ('onReady' in format) { + format.onReady.call(base); + } + }; + on(globalWin, 'load', loaded); + if (globalDoc.readyState === 'complete') { + loaded(); + } + }; + + initPlugins = function () { + var plugins = options.plugins; + + plugins = plugins ? plugins.toString().split(',') : []; + pluginManager = new PluginManager(base); + + plugins.forEach(function (plugin) { + pluginManager.register(plugin.trim()); + }); + }; + + /** + * Init the locale variable with the specified locale if possible + * @private + * @return void + */ + initLocale = function () { + var lang; + + locale = SCEditor.locale[options.locale]; + + if (!locale) { + lang = options.locale.split('-'); + locale = SCEditor.locale[lang[0]]; + } + + // Locale DateTime format overrides any specified in the options + if (locale && locale.dateFormat) { + options.dateFormat = locale.dateFormat; + } + }; + + /** + * Creates the editor iframe and textarea + * @private + */ + initEditor = function () { + sourceEditor = createElement('textarea'); + wysiwygEditor = createElement('iframe', { + frameborder: 0, + allowfullscreen: true + }); + + /* This needs to be done right after they are created because, + * for any reason, the user may not want the value to be tinkered + * by any filters. + */ + if (options.startInSourceMode) { + addClass(editorContainer, 'sourceMode'); + hide(wysiwygEditor); + } else { + addClass(editorContainer, 'wysiwygMode'); + hide(sourceEditor); + } + + if (!options.spellcheck) { + attr(editorContainer, 'spellcheck', 'false'); + } + + if (globalWin.location.protocol === 'https:') { + // eslint-disable-next-line no-script-url + attr(wysiwygEditor, 'src', 'javascript:false'); + } + + // Add the editor to the container + appendChild(editorContainer, wysiwygEditor); + appendChild(editorContainer, sourceEditor); + + // TODO: make this optional somehow + base.dimensions( + options.width || width(original), + options.height || height(original) + ); + + // Add IE version class to the HTML element so can apply + // conditional styling without CSS hacks + var className = IE_VER ? 'ie ie' + IE_VER : ''; + // Add ios to HTML so can apply CSS fix to only it + className += ios ? ' ios' : ''; + + wysiwygDocument = wysiwygEditor.contentDocument; + wysiwygDocument.open(); + wysiwygDocument.write(_tmpl('html', { + attrs: ' class="' + className + '"', + spellcheck: options.spellcheck ? '' : 'spellcheck="false"', + charset: options.charset, + style: options.style + })); + wysiwygDocument.close(); + + wysiwygBody = wysiwygDocument.body; + wysiwygWindow = wysiwygEditor.contentWindow; + + base.readOnly(!!options.readOnly); + + // iframe overflow fix for iOS, also fixes an IE issue with the + // editor not getting focus when clicking inside + if (ios || edge || IE_VER) { + height(wysiwygBody, '100%'); + + if (!IE_VER) { + on(wysiwygBody, 'touchend', base.focus); + } + } + + var tabIndex = attr(original, 'tabindex'); + attr(sourceEditor, 'tabindex', tabIndex); + attr(wysiwygEditor, 'tabindex', tabIndex); + + rangeHelper = new RangeHelper(wysiwygWindow); + + // load any textarea value into the editor + hide(original); + base.val(original.value); + + var placeholder = options.placeholder || + attr(original, 'placeholder'); + + if (placeholder) { + sourceEditor.placeholder = placeholder; + attr(wysiwygBody, 'placeholder', placeholder); + } + }; + + /** + * Initialises options + * @private + */ + initOptions = function () { + // auto-update original textbox on blur if option set to true + if (options.autoUpdate) { + on(wysiwygBody, 'blur', autoUpdate); + on(sourceEditor, 'blur', autoUpdate); + } + + if (options.rtl === null) { + options.rtl = css(sourceEditor, 'direction') === 'rtl'; + } + + base.rtl(!!options.rtl); + + if (options.autoExpand) { + // Need to update when images (or anything else) loads + on(wysiwygBody, 'load', autoExpand, EVENT_CAPTURE); + on(wysiwygBody, 'input keyup', autoExpand); + } + + if (options.resizeEnabled) { + initResize(); + } + + attr(editorContainer, 'id', options.id); + base.emoticons(options.emoticonsEnabled); + }; + + /** + * Initialises events + * @private + */ + initEvents = function () { + var form = original.form; + var compositionEvents = 'compositionstart compositionend'; + var eventsToForward = 'keydown keyup keypress focus blur contextmenu'; + var checkSelectionEvents = 'onselectionchange' in wysiwygDocument ? + 'selectionchange' : + 'keyup focus blur contextmenu mouseup touchend click'; + + on(globalDoc, 'click', handleDocumentClick); + + if (form) { + on(form, 'reset', handleFormReset); + on(form, 'submit', base.updateOriginal, EVENT_CAPTURE); + } + + on(wysiwygBody, 'keypress', handleKeyPress); + on(wysiwygBody, 'keydown', handleKeyDown); + on(wysiwygBody, 'keydown', handleBackSpace); + on(wysiwygBody, 'keyup', appendNewLine); + on(wysiwygBody, 'blur', valueChangedBlur); + on(wysiwygBody, 'keyup', valueChangedKeyUp); + on(wysiwygBody, 'paste', handlePasteEvt); + on(wysiwygBody, compositionEvents, handleComposition); + on(wysiwygBody, checkSelectionEvents, checkSelectionChanged); + on(wysiwygBody, eventsToForward, handleEvent); + + if (options.emoticonsCompat && globalWin.getSelection) { + on(wysiwygBody, 'keyup', emoticonsCheckWhitespace); + } + + on(wysiwygBody, 'blur', function () { + if (!base.val()) { + addClass(wysiwygBody, 'placeholder'); + } + }); + + on(wysiwygBody, 'focus', function () { + removeClass(wysiwygBody, 'placeholder'); + }); + + on(sourceEditor, 'blur', valueChangedBlur); + on(sourceEditor, 'keyup', valueChangedKeyUp); + on(sourceEditor, 'keydown', handleKeyDown); + on(sourceEditor, compositionEvents, handleComposition); + on(sourceEditor, eventsToForward, handleEvent); + + on(wysiwygDocument, 'mousedown', handleMouseDown); + on(wysiwygDocument, checkSelectionEvents, checkSelectionChanged); + on(wysiwygDocument, 'beforedeactivate keyup mouseup', saveRange); + on(wysiwygDocument, 'keyup', appendNewLine); + on(wysiwygDocument, 'focus', function () { + lastRange = null; + }); + + on(editorContainer, 'selectionchanged', checkNodeChanged); + on(editorContainer, 'selectionchanged', updateActiveButtons); + // Custom events to forward + on( + editorContainer, + 'selectionchanged valuechanged nodechanged pasteraw paste', + handleEvent + ); + }; + + /** + * Creates the toolbar and appends it to the container + * @private + */ + initToolBar = function () { + var group, + commands = base.commands, + exclude = (options.toolbarExclude || '').split(','), + groups = options.toolbar.split('|'); + + toolbar = createElement('div', { + className: 'sceditor-toolbar', + unselectable: 'on' + }); + + if (options.icons in SCEditor.icons) { + icons = new SCEditor.icons[options.icons](); + } + + each(groups, function (_, menuItems) { + group = createElement('div', { + className: 'sceditor-group' + }); + + each(menuItems.split(','), function (_, commandName) { + var button, shortcut, + command = commands[commandName]; + + // The commandName must be a valid command and not excluded + if (!command || exclude.indexOf(commandName) > -1) { + return; + } + + shortcut = command.shortcut; + button = _tmpl('toolbarButton', { + name: commandName, + dispName: base._(command.name || + command.tooltip || commandName) + }, true).firstChild; + + if (icons && icons.create) { + var icon = icons.create(commandName); + if (icon) { + insertBefore(icons.create(commandName), + button.firstChild); + addClass(button, 'has-icon'); + } + } + + button._sceTxtMode = !!command.txtExec; + button._sceWysiwygMode = !!command.exec; + toggleClass(button, 'disabled', !command.exec); + on(button, 'click', function (e) { + if (!hasClass(button, 'disabled')) { + handleCommand(button, command); + } + + updateActiveButtons(); + e.preventDefault(); + }); + // Prevent editor losing focus when button clicked + on(button, 'mousedown', function (e) { + base.closeDropDown(); + e.preventDefault(); + }); + + if (command.tooltip) { + attr(button, 'title', + base._(command.tooltip) + + (shortcut ? ' (' + shortcut + ')' : '') + ); + } + + if (shortcut) { + base.addShortcut(shortcut, commandName); + } + + if (command.state) { + btnStateHandlers.push({ + name: commandName, + state: command.state + }); + // exec string commands can be passed to queryCommandState + } else if (isString(command.exec)) { + btnStateHandlers.push({ + name: commandName, + state: command.exec + }); + } + + appendChild(group, button); + toolbarButtons[commandName] = button; + }); + + // Exclude empty groups + if (group.firstChild) { + appendChild(toolbar, group); + } + }); + + // Append the toolbar to the toolbarContainer option if given + appendChild(options.toolbarContainer || editorContainer, toolbar); + }; + + /** + * Creates the resizer. + * @private + */ + initResize = function () { + var minHeight, maxHeight, minWidth, maxWidth, + mouseMoveFunc, mouseUpFunc, + grip = createElement('div', { + className: 'sceditor-grip' + }), + // Cover is used to cover the editor iframe so document + // still gets mouse move events + cover = createElement('div', { + className: 'sceditor-resize-cover' + }), + moveEvents = 'touchmove mousemove', + endEvents = 'touchcancel touchend mouseup', + startX = 0, + startY = 0, + newX = 0, + newY = 0, + startWidth = 0, + startHeight = 0, + origWidth = width(editorContainer), + origHeight = height(editorContainer), + isDragging = false, + rtl = base.rtl(); + + minHeight = options.resizeMinHeight || origHeight / 1.5; + maxHeight = options.resizeMaxHeight || origHeight * 2.5; + minWidth = options.resizeMinWidth || origWidth / 1.25; + maxWidth = options.resizeMaxWidth || origWidth * 1.25; + + mouseMoveFunc = function (e) { + // iOS uses window.event + if (e.type === 'touchmove') { + e = globalWin.event; + newX = e.changedTouches[0].pageX; + newY = e.changedTouches[0].pageY; + } else { + newX = e.pageX; + newY = e.pageY; + } + + var newHeight = startHeight + (newY - startY), + newWidth = rtl ? + startWidth - (newX - startX) : + startWidth + (newX - startX); + + if (maxWidth > 0 && newWidth > maxWidth) { + newWidth = maxWidth; + } + if (minWidth > 0 && newWidth < minWidth) { + newWidth = minWidth; + } + if (!options.resizeWidth) { + newWidth = false; + } + + if (maxHeight > 0 && newHeight > maxHeight) { + newHeight = maxHeight; + } + if (minHeight > 0 && newHeight < minHeight) { + newHeight = minHeight; + } + if (!options.resizeHeight) { + newHeight = false; + } + + if (newWidth || newHeight) { + base.dimensions(newWidth, newHeight); + } + + e.preventDefault(); + }; + + mouseUpFunc = function (e) { + if (!isDragging) { + return; + } + + isDragging = false; + + hide(cover); + removeClass(editorContainer, 'resizing'); + off(globalDoc, moveEvents, mouseMoveFunc); + off(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }; + + if (icons && icons.create) { + var icon = icons.create('grip'); + if (icon) { + appendChild(grip, icon); + addClass(grip, 'has-icon'); + } + } + + appendChild(editorContainer, grip); + appendChild(editorContainer, cover); + hide(cover); + + on(grip, 'touchstart mousedown', function (e) { + // iOS uses window.event + if (e.type === 'touchstart') { + e = globalWin.event; + startX = e.touches[0].pageX; + startY = e.touches[0].pageY; + } else { + startX = e.pageX; + startY = e.pageY; + } + + startWidth = width(editorContainer); + startHeight = height(editorContainer); + isDragging = true; + + addClass(editorContainer, 'resizing'); + show(cover); + on(globalDoc, moveEvents, mouseMoveFunc); + on(globalDoc, endEvents, mouseUpFunc); + + e.preventDefault(); + }); + }; + + /** + * Prefixes and preloads the emoticon images + * @private + */ + initEmoticons = function () { + var emoticons = options.emoticons; + var root = options.emoticonsRoot || ''; + + if (emoticons) { + allEmoticons = extend( + {}, emoticons.more, emoticons.dropdown, emoticons.hidden + ); + } + + each(allEmoticons, function (key, url) { + allEmoticons[key] = _tmpl('emoticon', { + key: key, + // Prefix emoticon root to emoticon urls + url: root + (url.url || url), + tooltip: url.tooltip || key + }); + + // Preload the emoticon + if (options.emoticonsEnabled) { + preLoadCache.push(createElement('img', { + src: root + (url.url || url) + })); + } + }); + }; + + /** + * Autofocus the editor + * @private + */ + autofocus = function () { + var range, txtPos, + node = wysiwygBody.firstChild, + focusEnd = !!options.autofocusEnd; + + // Can't focus invisible elements + if (!isVisible(editorContainer)) { + return; + } + + if (base.sourceMode()) { + txtPos = focusEnd ? sourceEditor.value.length : 0; + + sourceEditor.setSelectionRange(txtPos, txtPos); + + return; + } + + removeWhiteSpace(wysiwygBody); + + if (focusEnd) { + if (!(node = wysiwygBody.lastChild)) { + node = createElement('p', {}, wysiwygDocument); + appendChild(wysiwygBody, node); + } + + while (node.lastChild) { + node = node.lastChild; + + // IE < 11 should place the cursor after the
    as + // it will show it as a newline. IE >= 11 and all + // other browsers should place the cursor before. + if (!IE_BR_FIX$2 && is(node, 'br') && node.previousSibling) { + node = node.previousSibling; + } + } + } + + range = wysiwygDocument.createRange(); + + if (!canHaveChildren(node)) { + range.setStartBefore(node); + + if (focusEnd) { + range.setStartAfter(node); + } + } else { + range.selectNodeContents(node); + } + + range.collapse(!focusEnd); + rangeHelper.selectRange(range); + currentSelection = range; + + if (focusEnd) { + wysiwygBody.scrollTop = wysiwygBody.scrollHeight; + } + + base.focus(); + }; + + /** + * Gets if the editor is read only + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly + * @return {boolean} + */ + /** + * Sets if the editor is read only + * + * @param {boolean} readOnly + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name readOnly^2 + * @return {this} + */ + base.readOnly = function (readOnly) { + if (typeof readOnly !== 'boolean') { + return !sourceEditor.readonly; + } + + wysiwygBody.contentEditable = !readOnly; + sourceEditor.readonly = !readOnly; + + updateToolBar(readOnly); + + return base; + }; + + /** + * Gets if the editor is in RTL mode + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl + * @return {boolean} + */ + /** + * Sets if the editor is in RTL mode + * + * @param {boolean} rtl + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name rtl^2 + * @return {this} + */ + base.rtl = function (rtl) { + var dir = rtl ? 'rtl' : 'ltr'; + + if (typeof rtl !== 'boolean') { + return attr(sourceEditor, 'dir') === 'rtl'; + } + + attr(wysiwygBody, 'dir', dir); + attr(sourceEditor, 'dir', dir); + + removeClass(editorContainer, 'rtl'); + removeClass(editorContainer, 'ltr'); + addClass(editorContainer, dir); + + if (icons && icons.rtl) { + icons.rtl(rtl); + } + + return base; + }; + + /** + * Updates the toolbar to disable/enable the appropriate buttons + * @private + */ + updateToolBar = function (disable) { + var mode = base.inSourceMode() ? '_sceTxtMode' : '_sceWysiwygMode'; + + each(toolbarButtons, function (_, button) { + toggleClass(button, 'disabled', disable || !button[mode]); + }); + }; + + /** + * Gets the width of the editor in pixels + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width + * @return {number} + */ + /** + * Sets the width of the editor + * + * @param {number} width Width in pixels + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name width^2 + * @return {this} + */ + /** + * Sets the width of the editor + * + * The saveWidth specifies if to save the width. The stored width can be + * used for things like restoring from maximized state. + * + * @param {number} width Width in pixels + * @param {boolean} [saveWidth=true] If to store the width + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name width^3 + * @return {this} + */ + base.width = function (width$$1, saveWidth) { + if (!width$$1 && width$$1 !== 0) { + return width(editorContainer); + } + + base.dimensions(width$$1, null, saveWidth); + + return base; + }; + + /** + * Returns an object with the properties width and height + * which are the width and height of the editor in px. + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions + * @return {object} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^2 + * @return {this} + */ + /** + *

    Sets the width and/or height of the editor.

    + * + *

    If width or height is not numeric it is ignored.

    + * + *

    The save argument specifies if to save the new sizes. + * The saved sizes can be used for things like restoring from + * maximized state. This should normally be left as true.

    + * + * @param {number} width Width in px + * @param {number} height Height in px + * @param {boolean} [save=true] If to store the new sizes + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name dimensions^3 + * @return {this} + */ + base.dimensions = function (width$$1, height$$1, save) { + // set undefined width/height to boolean false + width$$1 = (!width$$1 && width$$1 !== 0) ? false : width$$1; + height$$1 = (!height$$1 && height$$1 !== 0) ? false : height$$1; + + if (width$$1 === false && height$$1 === false) { + return { width: base.width(), height: base.height() }; + } + + if (width$$1 !== false) { + if (save !== false) { + options.width = width$$1; + } + + width(editorContainer, width$$1); + } + + if (height$$1 !== false) { + if (save !== false) { + options.height = height$$1; + } + + height(editorContainer, height$$1); + } + + return base; + }; + + /** + * Gets the height of the editor in px + * + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height + * @return {number} + */ + /** + * Sets the height of the editor + * + * @param {number} height Height in px + * @since 1.3.5 + * @function + * @memberOf SCEditor.prototype + * @name height^2 + * @return {this} + */ + /** + * Sets the height of the editor + * + * The saveHeight specifies if to save the height. + * + * The stored height can be used for things like + * restoring from maximized state. + * + * @param {number} height Height in px + * @param {boolean} [saveHeight=true] If to store the height + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name height^3 + * @return {this} + */ + base.height = function (height$$1, saveHeight) { + if (!height$$1 && height$$1 !== 0) { + return height(editorContainer); + } + + base.dimensions(null, height$$1, saveHeight); + + return base; + }; + + /** + * Gets if the editor is maximised or not + * + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize + * @return {boolean} + */ + /** + * Sets if the editor is maximised or not + * + * @param {boolean} maximize If to maximise the editor + * @since 1.4.1 + * @function + * @memberOf SCEditor.prototype + * @name maximize^2 + * @return {this} + */ + base.maximize = function (maximize) { + var maximizeSize = 'sceditor-maximize'; + + if (isUndefined(maximize)) { + return hasClass(editorContainer, maximizeSize); + } + + maximize = !!maximize; + + if (maximize) { + maximizeScrollPosition = globalWin.pageYOffset; + } + + toggleClass(globalDoc.documentElement, maximizeSize, maximize); + toggleClass(globalDoc.body, maximizeSize, maximize); + toggleClass(editorContainer, maximizeSize, maximize); + base.width(maximize ? '100%' : options.width, false); + base.height(maximize ? '100%' : options.height, false); + + if (!maximize) { + globalWin.scrollTo(0, maximizeScrollPosition); + } + + autoExpand(); + + return base; + }; + + autoExpand = function () { + if (options.autoExpand && !autoExpandThrottle) { + autoExpandThrottle = setTimeout(base.expandToContent, 200); + } + }; + + /** + * Expands or shrinks the editors height to the height of it's content + * + * Unless ignoreMaxHeight is set to true it will not expand + * higher than the maxHeight option. + * + * @since 1.3.5 + * @param {boolean} [ignoreMaxHeight=false] + * @function + * @name expandToContent + * @memberOf SCEditor.prototype + * @see #resizeToContent + */ + base.expandToContent = function (ignoreMaxHeight) { + if (base.maximize()) { + return; + } + + clearTimeout(autoExpandThrottle); + autoExpandThrottle = false; + + if (!autoExpandBounds) { + var height$$1 = options.resizeMinHeight || options.height || + height(original); + + autoExpandBounds = { + min: height$$1, + max: options.resizeMaxHeight || (height$$1 * 2) + }; + } + + var range = globalDoc.createRange(); + range.selectNodeContents(wysiwygBody); + + var rect = range.getBoundingClientRect(); + var current = wysiwygDocument.documentElement.clientHeight - 1; + var spaceNeeded = rect.bottom - rect.top; + var newHeight = base.height() + 1 + (spaceNeeded - current); + + if (!ignoreMaxHeight && autoExpandBounds.max !== -1) { + newHeight = Math.min(newHeight, autoExpandBounds.max); + } + + base.height(Math.ceil(Math.max(newHeight, autoExpandBounds.min))); + }; + + /** + * Destroys the editor, removing all elements and + * event handlers. + * + * Leaves only the original textarea. + * + * @function + * @name destroy + * @memberOf SCEditor.prototype + */ + base.destroy = function () { + // Don't destroy if the editor has already been destroyed + if (!pluginManager) { + return; + } + + pluginManager.destroy(); + + rangeHelper = null; + lastRange = null; + pluginManager = null; + + if (dropdown) { + remove(dropdown); + } + + off(globalDoc, 'click', handleDocumentClick); + + // TODO: make off support null nodes? + var form = original.form; + if (form) { + off(form, 'reset', handleFormReset); + off(form, 'submit', base.updateOriginal); + } + + remove(sourceEditor); + remove(toolbar); + remove(editorContainer); + + delete original._sceditor; + show(original); + + original.required = isRequired; + }; + + + /** + * Creates a menu item drop down + * + * @param {HTMLElement} menuItem The button to align the dropdown with + * @param {string} name Used for styling the dropdown, will be + * a class sceditor-name + * @param {HTMLElement} content The HTML content of the dropdown + * @param {boolean} ieFix If to add the unselectable attribute + * to all the contents elements. Stops + * IE from deselecting the text in the + * editor + * @function + * @name createDropDown + * @memberOf SCEditor.prototype + */ + base.createDropDown = function (menuItem, name, content, ieFix) { + // first click for create second click for close + var dropDownCss, + dropDownClass = 'sceditor-' + name; + + // Will re-focus the editor. This is needed for IE + // as it has special logic to save/restore the selection + base.closeDropDown(true); + + // Only close the dropdown if it was already open + if (dropdown && hasClass(dropdown, dropDownClass)) { + return; + } + + // IE needs unselectable attr to stop it from + // unselecting the text in the editor. + // SCEditor can cope if IE does unselect the + // text it's just not nice. + if (ieFix !== false) { + each(find(content, ':not(input):not(textarea)'), + function (_, node) { + if (node.nodeType === ELEMENT_NODE) { + attr(node, 'unselectable', 'on'); + } + }); + } + + dropDownCss = extend({ + top: menuItem.offsetTop, + left: menuItem.offsetLeft, + marginTop: menuItem.clientHeight + }, options.dropDownCss); + + dropdown = createElement('div', { + className: 'sceditor-dropdown ' + dropDownClass + }); + + css(dropdown, dropDownCss); + appendChild(dropdown, content); + appendChild(editorContainer, dropdown); + on(dropdown, 'click focusin', function (e) { + // stop clicks within the dropdown from being handled + e.stopPropagation(); + }); + + // If try to focus the first input immediately IE will + // place the cursor at the start of the editor instead + // of focusing on the input. + setTimeout(function () { + if (dropdown) { + var first = find(dropdown, 'input,textarea')[0]; + if (first) { + first.focus(); + } + } + }); + }; + + /** + * Handles any document click and closes the dropdown if open + * @private + */ + handleDocumentClick = function (e) { + // ignore right clicks + if (e.which !== 3 && dropdown && !e.defaultPrevented) { + autoUpdate(); + + base.closeDropDown(); + } + }; + + /** + * Handles the WYSIWYG editors paste event + * @private + */ + handlePasteEvt = function (e) { + var isIeOrEdge = IE_VER || edge; + var editable = wysiwygBody; + var clipboard = e.clipboardData; + var loadImage = function (file) { + var reader = new FileReader(); + reader.onload = function (e) { + handlePasteData({ + html: '' + }); + }; + reader.readAsDataURL(file); + }; + + // Modern browsers with clipboard API - everything other than _very_ + // old android web views and UC browser which doesn't support the + // paste event at all. + if (clipboard && !isIeOrEdge) { + var data$$1 = {}; + var types = clipboard.types; + var items = clipboard.items; + + e.preventDefault(); + + for (var i = 0; i < types.length; i++) { + // Normalise image pasting to paste as a data-uri + if (globalWin.FileReader && items && + IMAGE_MIME_REGEX.test(items[i].type)) { + return loadImage(clipboard.items[i].getAsFile()); + } + + data$$1[types[i]] = clipboard.getData(types[i]); + } + // Call plugins here with file? + data$$1.text = data$$1['text/plain']; + data$$1.html = data$$1['text/html']; + + handlePasteData(data$$1); + // If contentsFragment exists then we are already waiting for a + // previous paste so let the handler for that handle this one too + } else if (!pasteContentFragment) { + // Save the scroll position so can be restored + // when contents is restored + var scrollTop = editable.scrollTop; + + rangeHelper.saveRange(); + + pasteContentFragment = globalDoc.createDocumentFragment(); + while (editable.firstChild) { + appendChild(pasteContentFragment, editable.firstChild); + } + + setTimeout(function () { + var html = editable.innerHTML; + + editable.innerHTML = ''; + appendChild(editable, pasteContentFragment); + editable.scrollTop = scrollTop; + pasteContentFragment = false; + + rangeHelper.restoreRange(); + + handlePasteData({ html: html }); + }, 0); + } + }; + + /** + * Gets the pasted data, filters it and then inserts it. + * @param {Object} data + * @private + */ + handlePasteData = function (data$$1) { + var pasteArea = createElement('div', {}, wysiwygDocument); + + pluginManager.call('pasteRaw', data$$1); + trigger(editorContainer, 'pasteraw', data$$1); + + if (data$$1.html) { + pasteArea.innerHTML = data$$1.html; + + // fix any invalid nesting + fixNesting(pasteArea); + } else { + pasteArea.innerHTML = entities(data$$1.text || ''); + } + + var paste = { + val: pasteArea.innerHTML + }; + + if ('fragmentToSource' in format) { + paste.val = format + .fragmentToSource(paste.val, wysiwygDocument, currentNode); + } + + pluginManager.call('paste', paste); + trigger(editorContainer, 'paste', paste); + + if ('fragmentToHtml' in format) { + paste.val = format + .fragmentToHtml(paste.val, currentNode); + } + + pluginManager.call('pasteHtml', paste); + + base.wysiwygEditorInsertHtml(paste.val, null, true); + }; + + /** + * Closes any currently open drop down + * + * @param {boolean} [focus=false] If to focus the editor + * after closing the drop down + * @function + * @name closeDropDown + * @memberOf SCEditor.prototype + */ + base.closeDropDown = function (focus) { + if (dropdown) { + remove(dropdown); + dropdown = null; + } + + if (focus === true) { + base.focus(); + } + }; + + + /** + * Inserts HTML into WYSIWYG editor. + * + * If endHtml is specified, any selected text will be placed + * between html and endHtml. If there is no selected text html + * and endHtml will just be concatenate together. + * + * @param {string} html + * @param {string} [endHtml=null] + * @param {boolean} [overrideCodeBlocking=false] If to insert the html + * into code tags, by + * default code tags only + * support text. + * @function + * @name wysiwygEditorInsertHtml + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertHtml = function ( + html, endHtml, overrideCodeBlocking + ) { + var marker, scrollTop, scrollTo, + editorHeight = height(wysiwygEditor); + + base.focus(); + + // TODO: This code tag should be configurable and + // should maybe convert the HTML into text instead + // Don't apply to code elements + if (!overrideCodeBlocking && closest(currentBlockNode, 'code')) { + return; + } + + // Insert the HTML and save the range so the editor can be scrolled + // to the end of the selection. Also allows emoticons to be replaced + // without affecting the cursor position + rangeHelper.insertHTML(html, endHtml); + rangeHelper.saveRange(); + replaceEmoticons(); + + // Scroll the editor after the end of the selection + marker = find(wysiwygBody, '#sceditor-end-marker')[0]; + show(marker); + scrollTop = wysiwygBody.scrollTop; + scrollTo = (getOffset(marker).top + + (marker.offsetHeight * 1.5)) - editorHeight; + hide(marker); + + // Only scroll if marker isn't already visible + if (scrollTo > scrollTop || scrollTo + editorHeight < scrollTop) { + wysiwygBody.scrollTop = scrollTo; + } + + triggerValueChanged(false); + rangeHelper.restoreRange(); + + // Add a new line after the last block element + // so can always add text after it + appendNewLine(); + }; + + /** + * Like wysiwygEditorInsertHtml except it will convert any HTML + * into text before inserting it. + * + * @param {string} text + * @param {string} [endText=null] + * @function + * @name wysiwygEditorInsertText + * @memberOf SCEditor.prototype + */ + base.wysiwygEditorInsertText = function (text, endText) { + base.wysiwygEditorInsertHtml( + entities(text), entities(endText) + ); + }; + + /** + * Inserts text into the WYSIWYG or source editor depending on which + * mode the editor is in. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.3.5 + * @function + * @name insertText + * @memberOf SCEditor.prototype + */ + base.insertText = function (text, endText) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(text, endText); + } else { + base.wysiwygEditorInsertText(text, endText); + } + + return base; + }; + + /** + * Like wysiwygEditorInsertHtml but inserts text into the + * source mode editor instead. + * + * If endText is specified any selected text will be placed between + * text and endText. If no text is selected text and endText will + * just be concatenate together. + * + * The cursor will be placed after the text param. If endText is + * specified the cursor will be placed before endText, so passing:
    + * + * '[b]', '[/b]' + * + * Would cause the cursor to be placed:
    + * + * [b]Selected text|[/b] + * + * @param {string} text + * @param {string} [endText=null] + * @since 1.4.0 + * @function + * @name sourceEditorInsertText + * @memberOf SCEditor.prototype + */ + base.sourceEditorInsertText = function (text, endText) { + var scrollTop, currentValue, + startPos = sourceEditor.selectionStart, + endPos = sourceEditor.selectionEnd; + + scrollTop = sourceEditor.scrollTop; + sourceEditor.focus(); + currentValue = sourceEditor.value; + + if (endText) { + text += currentValue.substring(startPos, endPos) + endText; + } + + sourceEditor.value = currentValue.substring(0, startPos) + + text + + currentValue.substring(endPos, currentValue.length); + + sourceEditor.selectionStart = (startPos + text.length) - + (endText ? endText.length : 0); + sourceEditor.selectionEnd = sourceEditor.selectionStart; + + sourceEditor.scrollTop = scrollTop; + sourceEditor.focus(); + + triggerValueChanged(); + }; + + /** + * Gets the current instance of the rangeHelper class + * for the editor. + * + * @return {RangeHelper} + * @function + * @name getRangeHelper + * @memberOf SCEditor.prototype + */ + base.getRangeHelper = function () { + return rangeHelper; + }; + + /** + * Gets or sets the source editor caret position. + * + * @param {Object} [position] + * @return {this} + * @function + * @since 1.4.5 + * @name sourceEditorCaret + * @memberOf SCEditor.prototype + */ + base.sourceEditorCaret = function (position) { + sourceEditor.focus(); + + if (position) { + sourceEditor.selectionStart = position.start; + sourceEditor.selectionEnd = position.end; + + return this; + } + + return { + start: sourceEditor.selectionStart, + end: sourceEditor.selectionEnd + }; + }; + + /** + * Gets the value of the editor. + * + * If the editor is in WYSIWYG mode it will return the filtered + * HTML from it (converted to BBCode if using the BBCode plugin). + * It it's in Source Mode it will return the unfiltered contents + * of the source editor (if using the BBCode plugin this will be + * BBCode again). + * + * @since 1.3.5 + * @return {string} + * @function + * @name val + * @memberOf SCEditor.prototype + */ + /** + * Sets the value of the editor. + * + * If filter set true the val will be passed through the filter + * function. If using the BBCode plugin it will pass the val to + * the BBCode filter to convert any BBCode into HTML. + * + * @param {string} val + * @param {boolean} [filter=true] + * @return {this} + * @since 1.3.5 + * @function + * @name val^2 + * @memberOf SCEditor.prototype + */ + base.val = function (val, filter) { + if (!isString(val)) { + return base.inSourceMode() ? + base.getSourceEditorValue(false) : + base.getWysiwygEditorValue(filter); + } + + if (!base.inSourceMode()) { + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + base.setWysiwygEditorValue(val); + } else { + base.setSourceEditorValue(val); + } + + return base; + }; + + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @return {this} + * @since 1.3.5 + * @function + * @name insert + * @memberOf SCEditor.prototype + */ + /** + * Inserts HTML/BBCode into the editor + * + * If end is supplied any selected text will be placed between + * start and end. If there is no selected text start and end + * will be concatenate together. + * + * If the filter param is set to true, the HTML/BBCode will be + * passed through any plugin filters. If using the BBCode plugin + * this will convert any BBCode into HTML. + * + * If the allowMixed param is set to true, HTML any will not be + * escaped + * + * @param {string} start + * @param {string} [end=null] + * @param {boolean} [filter=true] + * @param {boolean} [convertEmoticons=true] If to convert emoticons + * @param {boolean} [allowMixed=false] + * @return {this} + * @since 1.4.3 + * @function + * @name insert^2 + * @memberOf SCEditor.prototype + */ + // eslint-disable-next-line max-params + base.insert = function ( + start, end, filter, convertEmoticons, allowMixed + ) { + if (base.inSourceMode()) { + base.sourceEditorInsertText(start, end); + return base; + } + + // Add the selection between start and end + if (end) { + var html = rangeHelper.selectedHtml(); + + if (filter !== false && 'fragmentToSource' in format) { + html = format + .fragmentToSource(html, wysiwygDocument, currentNode); + } + + start += html + end; + } + // TODO: This filter should allow empty tags as it's inserting. + if (filter !== false && 'fragmentToHtml' in format) { + start = format.fragmentToHtml(start, currentNode); + } + + // Convert any escaped HTML back into HTML if mixed is allowed + if (filter !== false && allowMixed === true) { + start = start.replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&'); + } + + base.wysiwygEditorInsertHtml(start); + + return base; + }; + + /** + * Gets the WYSIWYG editors HTML value. + * + * If using a plugin that filters the Ht Ml like the BBCode plugin + * it will return the result of the filtering (BBCode) unless the + * filter param is set to false. + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @name getWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.getWysiwygEditorValue = function (filter) { + var html; + // Create a tmp node to store contents so it can be modified + // without affecting anything else. + var tmp = createElement('div', {}, wysiwygDocument); + var childNodes = wysiwygBody.childNodes; + + for (var i = 0; i < childNodes.length; i++) { + appendChild(tmp, childNodes[i].cloneNode(true)); + } + + appendChild(wysiwygBody, tmp); + fixNesting(tmp); + remove(tmp); + + html = tmp.innerHTML; + + // filter the HTML and DOM through any plugins + if (filter !== false && format.hasOwnProperty('toSource')) { + html = format.toSource(html, wysiwygDocument); + } + + return html; + }; + + /** + * Gets the WYSIWYG editor's iFrame Body. + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getBody + * @memberOf SCEditor.prototype + */ + base.getBody = function () { + return wysiwygBody; + }; + + /** + * Gets the WYSIWYG editors container area (whole iFrame). + * + * @return {HTMLElement} + * @function + * @since 1.4.3 + * @name getContentAreaContainer + * @memberOf SCEditor.prototype + */ + base.getContentAreaContainer = function () { + return wysiwygEditor; + }; + + /** + * Gets the text editor value + * + * If using a plugin that filters the text like the BBCode plugin + * it will return the result of the filtering which is BBCode to + * HTML so it will return HTML. If filter is set to false it will + * just return the contents of the source editor (BBCode). + * + * @param {boolean} [filter=true] + * @return {string} + * @function + * @since 1.4.0 + * @name getSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.getSourceEditorValue = function (filter) { + var val = sourceEditor.value; + + if (filter !== false && 'toHtml' in format) { + val = format.toHtml(val); + } + + return val; + }; + + /** + * Sets the WYSIWYG HTML editor value. Should only be the HTML + * contained within the body tags + * + * @param {string} value + * @function + * @name setWysiwygEditorValue + * @memberOf SCEditor.prototype + */ + base.setWysiwygEditorValue = function (value) { + if (!value) { + value = '

    ' + (IE_VER ? '' : '
    ') + '

    '; + } + + wysiwygBody.innerHTML = value; + replaceEmoticons(); + + appendNewLine(); + triggerValueChanged(); + autoExpand(); + }; + + /** + * Sets the text editor value + * + * @param {string} value + * @function + * @name setSourceEditorValue + * @memberOf SCEditor.prototype + */ + base.setSourceEditorValue = function (value) { + sourceEditor.value = value; + + triggerValueChanged(); + }; + + /** + * Updates the textarea that the editor is replacing + * with the value currently inside the editor. + * + * @function + * @name updateOriginal + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.updateOriginal = function () { + original.value = base.val(); + }; + + /** + * Replaces any emoticon codes in the passed HTML + * with their emoticon images + * @private + */ + replaceEmoticons = function () { + if (options.emoticonsEnabled) { + replace(wysiwygBody, allEmoticons, options.emoticonsCompat); + } + }; + + /** + * If the editor is in source code mode + * + * @return {boolean} + * @function + * @name inSourceMode + * @memberOf SCEditor.prototype + */ + base.inSourceMode = function () { + return hasClass(editorContainer, 'sourceMode'); + }; + + /** + * Gets if the editor is in sourceMode + * + * @return boolean + * @function + * @name sourceMode + * @memberOf SCEditor.prototype + */ + /** + * Sets if the editor is in sourceMode + * + * @param {boolean} enable + * @return {this} + * @function + * @name sourceMode^2 + * @memberOf SCEditor.prototype + */ + base.sourceMode = function (enable) { + var inSourceMode = base.inSourceMode(); + + if (typeof enable !== 'boolean') { + return inSourceMode; + } + + if ((inSourceMode && !enable) || (!inSourceMode && enable)) { + base.toggleSourceMode(); + } + + return base; + }; + + /** + * Switches between the WYSIWYG and source modes + * + * @function + * @name toggleSourceMode + * @since 1.4.0 + * @memberOf SCEditor.prototype + */ + base.toggleSourceMode = function () { + var isInSourceMode = base.inSourceMode(); + + // don't allow switching to WYSIWYG if doesn't support it + if (!isWysiwygSupported && isInSourceMode) { + return; + } + + if (!isInSourceMode) { + rangeHelper.saveRange(); + rangeHelper.clear(); + } + + base.blur(); + + if (isInSourceMode) { + base.setWysiwygEditorValue(base.getSourceEditorValue()); + } else { + base.setSourceEditorValue(base.getWysiwygEditorValue()); + } + + lastRange = null; + toggle(sourceEditor); + toggle(wysiwygEditor); + + toggleClass(editorContainer, 'wysiwygMode', isInSourceMode); + toggleClass(editorContainer, 'sourceMode', !isInSourceMode); + + updateToolBar(); + updateActiveButtons(); + }; + + /** + * Gets the selected text of the source editor + * @return {string} + * @private + */ + sourceEditorSelectedText = function () { + sourceEditor.focus(); + + return sourceEditor.value.substring( + sourceEditor.selectionStart, + sourceEditor.selectionEnd + ); + }; + + /** + * Handles the passed command + * @private + */ + handleCommand = function (caller, cmd) { + // check if in text mode and handle text commands + if (base.inSourceMode()) { + if (cmd.txtExec) { + if (Array.isArray(cmd.txtExec)) { + base.sourceEditorInsertText.apply(base, cmd.txtExec); + } else { + cmd.txtExec.call(base, caller, sourceEditorSelectedText()); + } + } + } else if (cmd.exec) { + if (isFunction(cmd.exec)) { + cmd.exec.call(base, caller); + } else { + base.execCommand( + cmd.exec, + cmd.hasOwnProperty('execParam') ? cmd.execParam : null + ); + } + } + + }; + + /** + * Saves the current range. Needed for IE because it forgets + * where the cursor was and what was selected + * @private + */ + saveRange = function () { + /* this is only needed for IE */ + if (IE_VER) { + lastRange = rangeHelper.selectedRange(); + } + }; + + /** + * Executes a command on the WYSIWYG editor + * + * @param {string} command + * @param {String|Boolean} [param] + * @function + * @name execCommand + * @memberOf SCEditor.prototype + */ + base.execCommand = function (command, param) { + var executed = false, + commandObj = base.commands[command]; + + base.focus(); + + // TODO: make configurable + // don't apply any commands to code elements + if (closest(rangeHelper.parentNode(), 'code')) { + return; + } + + try { + executed = wysiwygDocument.execCommand(command, false, param); + } catch (ex) { } + + // show error if execution failed and an error message exists + if (!executed && commandObj && commandObj.errorMessage) { + /*global alert:false*/ + alert(base._(commandObj.errorMessage)); + } + + updateActiveButtons(); + }; + + /** + * Checks if the current selection has changed and triggers + * the selectionchanged event if it has. + * + * In browsers other than IE, it will check at most once every 100ms. + * This is because only IE has a selection changed event. + * @private + */ + checkSelectionChanged = function () { + function check() { + // Don't create new selection if there isn't one (like after + // blur event in iOS) + if (wysiwygWindow.getSelection() && + wysiwygWindow.getSelection().rangeCount <= 0) { + currentSelection = null; + // rangeHelper could be null if editor was destroyed + // before the timeout had finished + } else if (rangeHelper && !rangeHelper.compare(currentSelection)) { + currentSelection = rangeHelper.cloneSelected(); + + // If the selection is in an inline wrap it in a block. + // Fixes #331 + if (currentSelection && currentSelection.collapsed) { + var parent$$1 = currentSelection.startContainer; + var offset = currentSelection.startOffset; + + // Handle if selection is placed before/after an element + if (offset && parent$$1.nodeType !== TEXT_NODE) { + parent$$1 = parent$$1.childNodes[offset]; + } + + while (parent$$1 && parent$$1.parentNode !== wysiwygBody) { + parent$$1 = parent$$1.parentNode; + } + + if (parent$$1 && isInline(parent$$1, true)) { + rangeHelper.saveRange(); + wrapInlines(wysiwygBody, wysiwygDocument); + rangeHelper.restoreRange(); + } + } + + trigger(editorContainer, 'selectionchanged'); + } + + isSelectionCheckPending = false; + } + + if (isSelectionCheckPending) { + return; + } + + isSelectionCheckPending = true; + + // Don't need to limit checking if browser supports the Selection API + if ('onselectionchange' in wysiwygDocument) { + check(); + } else { + setTimeout(check, 100); + } + }; + + /** + * Checks if the current node has changed and triggers + * the nodechanged event if it has + * @private + */ + checkNodeChanged = function () { + // check if node has changed + var oldNode, + node = rangeHelper.parentNode(); + + if (currentNode !== node) { + oldNode = currentNode; + currentNode = node; + currentBlockNode = rangeHelper.getFirstBlockParent(node); + + trigger(editorContainer, 'nodechanged', { + oldNode: oldNode, + newNode: currentNode + }); + } + }; + + /** + * Gets the current node that contains the selection/caret in + * WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentNode + * @memberOf SCEditor.prototype + */ + base.currentNode = function () { + return currentNode; + }; + + /** + * Gets the first block level node that contains the + * selection/caret in WYSIWYG mode. + * + * Will be null in sourceMode or if there is no selection. + * + * @return {?Node} + * @function + * @name currentBlockNode + * @memberOf SCEditor.prototype + * @since 1.4.4 + */ + base.currentBlockNode = function () { + return currentBlockNode; + }; + + /** + * Updates if buttons are active or not + * @private + */ + updateActiveButtons = function () { + var firstBlock, parent$$1; + var activeClass = 'active'; + var doc = wysiwygDocument; + var isSource = base.sourceMode(); + + if (base.readOnly()) { + each(find(toolbar, activeClass), function (_, menuItem) { + removeClass(menuItem, activeClass); + }); + return; + } + + if (!isSource) { + parent$$1 = rangeHelper.parentNode(); + firstBlock = rangeHelper.getFirstBlockParent(parent$$1); + } + + for (var j = 0; j < btnStateHandlers.length; j++) { + var state = 0; + var btn = toolbarButtons[btnStateHandlers[j].name]; + var stateFn = btnStateHandlers[j].state; + var isDisabled = (isSource && !btn._sceTxtMode) || + (!isSource && !btn._sceWysiwygMode); + + if (isString(stateFn)) { + if (!isSource) { + try { + state = doc.queryCommandEnabled(stateFn) ? 0 : -1; + + // eslint-disable-next-line max-depth + if (state > -1) { + state = doc.queryCommandState(stateFn) ? 1 : 0; + } + } catch (ex) {} + } + } else if (!isDisabled) { + state = stateFn.call(base, parent$$1, firstBlock); + } + + toggleClass(btn, 'disabled', isDisabled || state < 0); + toggleClass(btn, activeClass, state > 0); + } + + if (icons && icons.update) { + icons.update(isSource, parent$$1, firstBlock); + } + }; + + /** + * Handles any key press in the WYSIWYG editor + * + * @private + */ + handleKeyPress = function (e) { + // FF bug: https://bugzilla.mozilla.org/show_bug.cgi?id=501496 + if (e.defaultPrevented) { + return; + } + + base.closeDropDown(); + + // 13 = enter key + if (e.which === 13) { + var LIST_TAGS = 'li,ul,ol'; + + // "Fix" (cludge) for blocklevel elements being duplicated in some + // browsers when enter is pressed instead of inserting a newline + if (!is(currentBlockNode, LIST_TAGS) && + hasStyling(currentBlockNode)) { + lastRange = null; + + var br = createElement('br', {}, wysiwygDocument); + rangeHelper.insertNode(br); + + // Last
    of a block will be collapsed unless it is + // IE < 11 so need to make sure the
    that was inserted + // isn't the last node of a block. + if (!IE_BR_FIX$2) { + var parent$$1 = br.parentNode; + var lastChild = parent$$1.lastChild; + + // Sometimes an empty next node is created after the
    + if (lastChild && lastChild.nodeType === TEXT_NODE && + lastChild.nodeValue === '') { + remove(lastChild); + lastChild = parent$$1.lastChild; + } + + // If this is the last BR of a block and the previous + // sibling is inline then will need an extra BR. This + // is needed because the last BR of a block will be + // collapsed. Fixes issue #248 + if (!isInline(parent$$1, true) && lastChild === br && + isInline(br.previousSibling)) { + rangeHelper.insertHTML('
    '); + } + } + + e.preventDefault(); + } + } + }; + + /** + * Makes sure that if there is a code or quote tag at the + * end of the editor, that there is a new line after it. + * + * If there wasn't a new line at the end you wouldn't be able + * to enter any text after a code/quote tag + * @return {void} + * @private + */ + appendNewLine = function () { + // Check all nodes in reverse until either add a new line + // or reach a non-empty textnode or BR at which point can + // stop checking. + rTraverse(wysiwygBody, function (node) { + // Last block, add new line after if has styling + if (node.nodeType === ELEMENT_NODE && + !/inline/.test(css(node, 'display'))) { + + // Add line break after if has styling + if (!is(node, '.sceditor-nlf') && hasStyling(node)) { + var paragraph = createElement('p', {}, wysiwygDocument); + paragraph.className = 'sceditor-nlf'; + paragraph.innerHTML = !IE_BR_FIX$2 ? '
    ' : ''; + appendChild(wysiwygBody, paragraph); + return false; + } + } + + // Last non-empty text node or line break. + // No need to add line-break after them + if ((node.nodeType === 3 && !/^\s*$/.test(node.nodeValue)) || + is(node, 'br')) { + return false; + } + }); + }; + + /** + * Handles form reset event + * @private + */ + handleFormReset = function () { + base.val(original.value); + }; + + /** + * Handles any mousedown press in the WYSIWYG editor + * @private + */ + handleMouseDown = function () { + base.closeDropDown(); + lastRange = null; + }; + + /** + * Translates the string into the locale language. + * + * Replaces any {0}, {1}, {2}, ect. with the params provided. + * + * @param {string} str + * @param {...String} args + * @return {string} + * @function + * @name _ + * @memberOf SCEditor.prototype + */ + base._ = function () { + var undef, + args = arguments; + + if (locale && locale[args[0]]) { + args[0] = locale[args[0]]; + } + + return args[0].replace(/\{(\d+)\}/g, function (str, p1) { + return args[p1 - 0 + 1] !== undef ? + args[p1 - 0 + 1] : + '{' + p1 + '}'; + }); + }; + + /** + * Passes events on to any handlers + * @private + * @return void + */ + handleEvent = function (e) { + if (pluginManager) { + // Send event to all plugins + pluginManager.call(e.type + 'Event', e, base); + } + + // convert the event into a custom event to send + var name = (e.target === sourceEditor ? 'scesrc' : 'scewys') + e.type; + + if (eventHandlers[name]) { + eventHandlers[name].forEach(function (fn) { + fn.call(base, e); + }); + } + }; + + /** + * Binds a handler to the specified events + * + * This function only binds to a limited list of + * supported events. + * + * The supported events are: + * + * * keyup + * * keydown + * * Keypress + * * blur + * * focus + * * nodechanged - When the current node containing + * the selection changes in WYSIWYG mode + * * contextmenu + * * selectionchanged + * * valuechanged + * + * + * The events param should be a string containing the event(s) + * to bind this handler to. If multiple, they should be separated + * by spaces. + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name bind + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.bind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + var wysEvent = 'scewys' + events[i]; + var srcEvent = 'scesrc' + events[i]; + // Use custom events to allow passing the instance as the + // 2nd argument. + // Also allows unbinding without unbinding the editors own + // event handlers. + if (!excludeWysiwyg) { + eventHandlers[wysEvent] = eventHandlers[wysEvent] || []; + eventHandlers[wysEvent].push(handler); + } + + if (!excludeSource) { + eventHandlers[srcEvent] = eventHandlers[srcEvent] || []; + eventHandlers[srcEvent].push(handler); + } + + // Start sending value changed events + if (events[i] === 'valuechanged') { + triggerValueChanged.hasHandler = true; + } + } + } + + return base; + }; + + /** + * Unbinds an event that was bound using bind(). + * + * @param {string} events + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude unbinding this + * handler from the WYSIWYG editor + * @param {boolean} excludeSource if to exclude unbinding this + * handler from the source editor + * @return {this} + * @function + * @name unbind + * @memberOf SCEditor.prototype + * @since 1.4.1 + * @see bind + */ + base.unbind = function (events, handler, excludeWysiwyg, excludeSource) { + events = events.split(' '); + + var i = events.length; + while (i--) { + if (isFunction(handler)) { + if (!excludeWysiwyg) { + arrayRemove( + eventHandlers['scewys' + events[i]] || [], handler); + } + + if (!excludeSource) { + arrayRemove( + eventHandlers['scesrc' + events[i]] || [], handler); + } + } + } + + return base; + }; + + /** + * Blurs the editors input area + * + * @return {this} + * @function + * @name blur + * @memberOf SCEditor.prototype + * @since 1.3.6 + */ + /** + * Adds a handler to the editors blur event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name blur^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.blur = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('blur', handler, excludeWysiwyg, excludeSource); + } else if (!base.sourceMode()) { + wysiwygBody.blur(); + } else { + sourceEditor.blur(); + } + + return base; + }; + + /** + * Focuses the editors input area + * + * @return {this} + * @function + * @name focus + * @memberOf SCEditor.prototype + */ + /** + * Adds an event handler to the focus event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource if to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name focus^2 + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.focus = function (handler, excludeWysiwyg, excludeSource) { + if (isFunction(handler)) { + base.bind('focus', handler, excludeWysiwyg, excludeSource); + } else if (!base.inSourceMode()) { + // Already has focus so do nothing + if (find(wysiwygDocument, ':focus').length) { + return; + } + + var container; + var rng = rangeHelper.selectedRange(); + + // Fix FF bug where it shows the cursor in the wrong place + // if the editor hasn't had focus before. See issue #393 + if (!currentSelection) { + autofocus(); + } + + // Check if cursor is set after a BR when the BR is the only + // child of the parent. In Firefox this causes a line break + // to occur when something is typed. See issue #321 + if (!IE_BR_FIX$2 && rng && rng.endOffset === 1 && rng.collapsed) { + container = rng.endContainer; + + if (container && container.childNodes.length === 1 && + is(container.firstChild, 'br')) { + rng.setStartBefore(container.firstChild); + rng.collapse(true); + rangeHelper.selectRange(rng); + } + } + + wysiwygWindow.focus(); + wysiwygBody.focus(); + + // Needed for IE + if (lastRange) { + rangeHelper.selectRange(lastRange); + + // Remove the stored range after being set. + // If the editor loses focus it should be saved again. + lastRange = null; + } + } else { + sourceEditor.focus(); + } + + updateActiveButtons(); + + return base; + }; + + /** + * Adds a handler to the key down event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyDown + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyDown = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keydown', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key press event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyPress + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyPress = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('keypress', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the key up event + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name keyUp + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.keyUp = function (handler, excludeWysiwyg, excludeSource) { + return base.bind('keyup', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Adds a handler to the node changed event. + * + * Happens whenever the node containing the selection/caret + * changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name nodeChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.nodeChanged = function (handler) { + return base.bind('nodechanged', handler, false, true); + }; + + /** + * Adds a handler to the selection changed event + * + * Happens whenever the selection changes in WYSIWYG mode. + * + * @param {Function} handler + * @return {this} + * @function + * @name selectionChanged + * @memberOf SCEditor.prototype + * @since 1.4.1 + */ + base.selectionChanged = function (handler) { + return base.bind('selectionchanged', handler, false, true); + }; + + /** + * Adds a handler to the value changed event + * + * Happens whenever the current editor value changes. + * + * Whenever anything is inserted, the value changed or + * 1.5 secs after text is typed. If a space is typed it will + * cause the event to be triggered immediately instead of + * after 1.5 seconds + * + * @param {Function} handler + * @param {boolean} excludeWysiwyg If to exclude adding this handler + * to the WYSIWYG editor + * @param {boolean} excludeSource If to exclude adding this handler + * to the source editor + * @return {this} + * @function + * @name valueChanged + * @memberOf SCEditor.prototype + * @since 1.4.5 + */ + base.valueChanged = function (handler, excludeWysiwyg, excludeSource) { + return base + .bind('valuechanged', handler, excludeWysiwyg, excludeSource); + }; + + /** + * Emoticons keypress handler + * @private + */ + emoticonsKeyPress = function (e) { + var replacedEmoticon, + cachePos = 0, + emoticonsCache = base.emoticonsCache, + curChar = String.fromCharCode(e.which); + + // TODO: Make configurable + if (closest(currentBlockNode, 'code')) { + return; + } + + if (!emoticonsCache) { + emoticonsCache = []; + + each(allEmoticons, function (key, html) { + emoticonsCache[cachePos++] = [key, html]; + }); + + emoticonsCache.sort(function (a, b) { + return a[0].length - b[0].length; + }); + + base.emoticonsCache = emoticonsCache; + base.longestEmoticonCode = + emoticonsCache[emoticonsCache.length - 1][0].length; + } + + replacedEmoticon = rangeHelper.replaceKeyword( + base.emoticonsCache, + true, + true, + base.longestEmoticonCode, + options.emoticonsCompat, + curChar + ); + + if (replacedEmoticon) { + if (!options.emoticonsCompat || !/^\s$/.test(curChar)) { + e.preventDefault(); + } + } + }; + + /** + * Makes sure emoticons are surrounded by whitespace + * @private + */ + emoticonsCheckWhitespace = function () { + checkWhitespace(currentBlockNode, rangeHelper); + }; + + /** + * Gets if emoticons are currently enabled + * @return {boolean} + * @function + * @name emoticons + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + /** + * Enables/disables emoticons + * + * @param {boolean} enable + * @return {this} + * @function + * @name emoticons^2 + * @memberOf SCEditor.prototype + * @since 1.4.2 + */ + base.emoticons = function (enable) { + if (!enable && enable !== false) { + return options.emoticonsEnabled; + } + + options.emoticonsEnabled = enable; + + if (enable) { + on(wysiwygBody, 'keypress', emoticonsKeyPress); + + if (!base.sourceMode()) { + rangeHelper.saveRange(); + + replaceEmoticons(); + triggerValueChanged(false); + + rangeHelper.restoreRange(); + } + } else { + var emoticons = + find(wysiwygBody, 'img[data-sceditor-emoticon]'); + + each(emoticons, function (_, img) { + var text = data(img, 'sceditor-emoticon'); + var textNode = wysiwygDocument.createTextNode(text); + img.parentNode.replaceChild(textNode, img); + }); + + off(wysiwygBody, 'keypress', emoticonsKeyPress); + + triggerValueChanged(); + } + + return base; + }; + + /** + * Gets the current WYSIWYG editors inline CSS + * + * @return {string} + * @function + * @name css + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + /** + * Sets inline CSS for the WYSIWYG editor + * + * @param {string} css + * @return {this} + * @function + * @name css^2 + * @memberOf SCEditor.prototype + * @since 1.4.3 + */ + base.css = function (css$$1) { + if (!inlineCss) { + inlineCss = createElement('style', { + id: 'inline' + }, wysiwygDocument); + + appendChild(wysiwygDocument.head, inlineCss); + } + + if (!isString(css$$1)) { + return inlineCss.styleSheet ? + inlineCss.styleSheet.cssText : inlineCss.innerHTML; + } + + if (inlineCss.styleSheet) { + inlineCss.styleSheet.cssText = css$$1; + } else { + inlineCss.innerHTML = css$$1; + } + + return base; + }; + + /** + * Handles the keydown event, used for shortcuts + * @private + */ + handleKeyDown = function (e) { + var shortcut = [], + SHIFT_KEYS = { + '`': '~', + '1': '!', + '2': '@', + '3': '#', + '4': '$', + '5': '%', + '6': '^', + '7': '&', + '8': '*', + '9': '(', + '0': ')', + '-': '_', + '=': '+', + ';': ': ', + '\'': '"', + ',': '<', + '.': '>', + '/': '?', + '\\': '|', + '[': '{', + ']': '}' + }, + SPECIAL_KEYS = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 19: 'pause', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'insert', + 46: 'del', + 91: 'win', + 92: 'win', + 93: 'select', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9', + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111: '/', + 112: 'f1', + 113: 'f2', + 114: 'f3', + 115: 'f4', + 116: 'f5', + 117: 'f6', + 118: 'f7', + 119: 'f8', + 120: 'f9', + 121: 'f10', + 122: 'f11', + 123: 'f12', + 144: 'numlock', + 145: 'scrolllock', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: '\'' + }, + NUMPAD_SHIFT_KEYS = { + 109: '-', + 110: 'del', + 111: '/', + 96: '0', + 97: '1', + 98: '2', + 99: '3', + 100: '4', + 101: '5', + 102: '6', + 103: '7', + 104: '8', + 105: '9' + }, + which = e.which, + character = SPECIAL_KEYS[which] || + String.fromCharCode(which).toLowerCase(); + + if (e.ctrlKey || e.metaKey) { + shortcut.push('ctrl'); + } + + if (e.altKey) { + shortcut.push('alt'); + } + + if (e.shiftKey) { + shortcut.push('shift'); + + if (NUMPAD_SHIFT_KEYS[which]) { + character = NUMPAD_SHIFT_KEYS[which]; + } else if (SHIFT_KEYS[character]) { + character = SHIFT_KEYS[character]; + } + } + + // Shift is 16, ctrl is 17 and alt is 18 + if (character && (which < 16 || which > 18)) { + shortcut.push(character); + } + + shortcut = shortcut.join('+'); + if (shortcutHandlers[shortcut] && + shortcutHandlers[shortcut].call(base) === false) { + + e.stopPropagation(); + e.preventDefault(); + } + }; + + /** + * Adds a shortcut handler to the editor + * @param {string} shortcut + * @param {String|Function} cmd + * @return {sceditor} + */ + base.addShortcut = function (shortcut, cmd) { + shortcut = shortcut.toLowerCase(); + + if (isString(cmd)) { + shortcutHandlers[shortcut] = function () { + handleCommand(toolbarButtons[cmd], base.commands[cmd]); + + return false; + }; + } else { + shortcutHandlers[shortcut] = cmd; + } + + return base; + }; + + /** + * Removes a shortcut handler + * @param {string} shortcut + * @return {sceditor} + */ + base.removeShortcut = function (shortcut) { + delete shortcutHandlers[shortcut.toLowerCase()]; + + return base; + }; + + /** + * Handles the backspace key press + * + * Will remove block styling like quotes/code ect if at the start. + * @private + */ + handleBackSpace = function (e) { + var node, offset, range, parent$$1; + + // 8 is the backspace key + if (options.disableBlockRemove || e.which !== 8 || + !(range = rangeHelper.selectedRange())) { + return; + } + + node = range.startContainer; + offset = range.startOffset; + + if (offset !== 0 || !(parent$$1 = currentStyledBlockNode()) || + is(parent$$1, 'body')) { + return; + } + + while (node !== parent$$1) { + while (node.previousSibling) { + node = node.previousSibling; + + // Everything but empty text nodes before the cursor + // should prevent the style from being removed + if (node.nodeType !== TEXT_NODE || node.nodeValue) { + return; + } + } + + if (!(node = node.parentNode)) { + return; + } + } + + // The backspace was pressed at the start of + // the container so clear the style + base.clearBlockFormatting(parent$$1); + e.preventDefault(); + }; + + /** + * Gets the first styled block node that contains the cursor + * @return {HTMLElement} + */ + currentStyledBlockNode = function () { + var block = currentBlockNode; + + while (!hasStyling(block) || isInline(block, true)) { + if (!(block = block.parentNode) || is(block, 'body')) { + return; + } + } + + return block; + }; + + /** + * Clears the formatting of the passed block element. + * + * If block is false, if will clear the styling of the first + * block level element that contains the cursor. + * @param {HTMLElement} block + * @since 1.4.4 + */ + base.clearBlockFormatting = function (block) { + block = block || currentStyledBlockNode(); + + if (!block || is(block, 'body')) { + return base; + } + + rangeHelper.saveRange(); + + block.className = ''; + lastRange = null; + + attr(block, 'style', ''); + + if (!is(block, 'p,div,td')) { + convertElement(block, 'p'); + } + + rangeHelper.restoreRange(); + return base; + }; + + /** + * Triggers the valueChanged signal if there is + * a plugin that handles it. + * + * If rangeHelper.saveRange() has already been + * called, then saveRange should be set to false + * to prevent the range being saved twice. + * + * @since 1.4.5 + * @param {boolean} saveRange If to call rangeHelper.saveRange(). + * @private + */ + triggerValueChanged = function (saveRange) { + if (!pluginManager || + (!pluginManager.hasHandler('valuechangedEvent') && + !triggerValueChanged.hasHandler)) { + return; + } + + var currentHtml, + sourceMode = base.sourceMode(), + hasSelection = !sourceMode && rangeHelper.hasSelection(); + + // Composition end isn't guaranteed to fire but must have + // ended when triggerValueChanged() is called so reset it + isComposing = false; + + // Don't need to save the range if sceditor-start-marker + // is present as the range is already saved + saveRange = saveRange !== false && + !wysiwygDocument.getElementById('sceditor-start-marker'); + + // Clear any current timeout as it's now been triggered + if (valueChangedKeyUpTimer) { + clearTimeout(valueChangedKeyUpTimer); + valueChangedKeyUpTimer = false; + } + + if (hasSelection && saveRange) { + rangeHelper.saveRange(); + } + + currentHtml = sourceMode ? sourceEditor.value : wysiwygBody.innerHTML; + + // Only trigger if something has actually changed. + if (currentHtml !== triggerValueChanged.lastVal) { + triggerValueChanged.lastVal = currentHtml; + + trigger(editorContainer, 'valuechanged', { + rawValue: sourceMode ? base.val() : currentHtml + }); + } + + if (hasSelection && saveRange) { + rangeHelper.removeMarkers(); + } + }; + + /** + * Should be called whenever there is a blur event + * @private + */ + valueChangedBlur = function () { + if (valueChangedKeyUpTimer) { + triggerValueChanged(); + } + }; + + /** + * Should be called whenever there is a keypress event + * @param {Event} e The keypress event + * @private + */ + valueChangedKeyUp = function (e) { + var which = e.which, + lastChar = valueChangedKeyUp.lastChar, + lastWasSpace = (lastChar === 13 || lastChar === 32), + lastWasDelete = (lastChar === 8 || lastChar === 46); + + valueChangedKeyUp.lastChar = which; + + if (isComposing) { + return; + } + + // 13 = return & 32 = space + if (which === 13 || which === 32) { + if (!lastWasSpace) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + // 8 = backspace & 46 = del + } else if (which === 8 || which === 46) { + if (!lastWasDelete) { + triggerValueChanged(); + } else { + valueChangedKeyUp.triggerNext = true; + } + } else if (valueChangedKeyUp.triggerNext) { + triggerValueChanged(); + valueChangedKeyUp.triggerNext = false; + } + + // Clear the previous timeout and set a new one. + clearTimeout(valueChangedKeyUpTimer); + + // Trigger the event 1.5s after the last keypress if space + // isn't pressed. This might need to be lowered, will need + // to look into what the slowest average Chars Per Min is. + valueChangedKeyUpTimer = setTimeout(function () { + if (!isComposing) { + triggerValueChanged(); + } + }, 1500); + }; + + handleComposition = function (e) { + isComposing = /start/i.test(e.type); + + if (!isComposing) { + triggerValueChanged(); + } + }; + + autoUpdate = function () { + base.updateOriginal(); + }; + + // run the initializer + init(); + } + + + /** + * Map containing the loaded SCEditor locales + * @type {Object} + * @name locale + * @memberOf sceditor + */ + SCEditor.locale = {}; + + SCEditor.formats = {}; + SCEditor.icons = {}; + + + /** + * Static command helper class + * @class command + * @name sceditor.command + */ + SCEditor.command = + /** @lends sceditor.command */ + { + /** + * Gets a command + * + * @param {string} name + * @return {Object|null} + * @since v1.3.5 + */ + get: function (name) { + return defaultCmds[name] || null; + }, + + /** + *

    Adds a command to the editor or updates an existing + * command if a command with the specified name already exists.

    + * + *

    Once a command is add it can be included in the toolbar by + * adding it's name to the toolbar option in the constructor. It + * can also be executed manually by calling + * {@link sceditor.execCommand}

    + * + * @example + * SCEditor.command.set("hello", + * { + * exec: function () { + * alert("Hello World!"); + * } + * }); + * + * @param {string} name + * @param {Object} cmd + * @return {this|false} Returns false if name or cmd is false + * @since v1.3.5 + */ + set: function (name, cmd) { + if (!name || !cmd) { + return false; + } + + // merge any existing command properties + cmd = extend(defaultCmds[name] || {}, cmd); + + cmd.remove = function () { + SCEditor.command.remove(name); + }; + + defaultCmds[name] = cmd; + return this; + }, + + /** + * Removes a command + * + * @param {string} name + * @return {this} + * @since v1.3.5 + */ + remove: function (name) { + if (defaultCmds[name]) { + delete defaultCmds[name]; + } + + return this; + } + }; + + /** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor + * @author Sam Clarke + */ + + window.sceditor = { + command: SCEditor.command, + commands: defaultCmds, + defaultOptions: defaultOptions, + + ie: ie, + ios: ios, + isWysiwygSupported: isWysiwygSupported, + + regexEscape: regex, + escapeEntities: entities, + escapeUriScheme: uriScheme, + + dom: { + css: css, + attr: attr, + removeAttr: removeAttr, + is: is, + closest: closest, + width: width, + height: height, + traverse: traverse, + rTraverse: rTraverse, + parseHTML: parseHTML, + hasStyling: hasStyling, + convertElement: convertElement, + blockLevelList: blockLevelList, + canHaveChildren: canHaveChildren, + isInline: isInline, + copyCSS: copyCSS, + fixNesting: fixNesting, + findCommonAncestor: findCommonAncestor, + getSibling: getSibling, + removeWhiteSpace: removeWhiteSpace, + extractContents: extractContents, + getOffset: getOffset, + getStyle: getStyle, + hasStyle: hasStyle + }, + locale: SCEditor.locale, + icons: SCEditor.icons, + utils: { + each: each, + isEmptyObject: isEmptyObject, + extend: extend + }, + plugins: PluginManager.plugins, + formats: SCEditor.formats, + create: function (textarea, options) { + options = options || {}; + + // Don't allow the editor to be initialised + // on it's own source editor + if (parent(textarea, '.sceditor-container')) { + return; + } + + if (options.runWithoutWysiwygSupport || isWysiwygSupported) { + /*eslint no-new: off*/ + (new SCEditor(textarea, options)); + } + }, + instance: function (textarea) { + return textarea._sceditor; + } + }; + +}()); diff --git a/public/assets/development/themes/content/default.css b/public/assets/development/themes/content/default.css new file mode 100644 index 0000000..4cf0e68 --- /dev/null +++ b/public/assets/development/themes/content/default.css @@ -0,0 +1,85 @@ +/*! SCEditor | (C) 2011-2013, Sam Clarke | sceditor.com/license */ +html, body, p, code:before, table { + margin: 0; + padding: 0; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + line-height: 1.25; + overflow: visible; +} +html { + height: 100%; +} +.ios { + /* Needed for iOS scrolling bug fix */ + overflow: auto; + -webkit-overflow-scrolling: touch; +} +.ios body { + /* Needed for iOS scrolling bug fix */ + position: relative; + overflow: auto; +} +body { + /* Needed to make sure body covers the whole editor and that + long lines don't cause horizontal scrolling */ + min-height: 100%; + word-wrap: break-word; +} + +body.placeholder::before { + content: attr(placeholder); + color: #555; + font-style: italic; +} + +ul, ol { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; +} + +table, td { + border: 1px dotted #000; + empty-cells: show; +} + +table td { + min-width: 5px; +} + +code { + display: block; + background: #f1f1f1; + white-space: pre; + padding: 1em; + text-align: left; + margin: .25em 0; + direction: ltr; +} + +blockquote { + background: #fff7d9; + margin: .25em 0; + border-left: .3em solid #f4e59f; + padding: .5em .5em .5em .75em; +} +blockquote cite { + font-weight: bold; + display: block; + font-size: 1em; + margin: 0 -.5em .25em -.75em; + padding: 0 .5em .15em .75em; + border-bottom: 1px solid #f4e59f; +} + +h1, h2, h3, h4, h5, h6 { + padding: 0; margin: 0; +} + +/* Prevent empty paragraphs from collapsing */ +div, p { + min-height: 1.25em; +} diff --git a/public/assets/development/themes/default.css b/public/assets/development/themes/default.css new file mode 100644 index 0000000..6d0de8b --- /dev/null +++ b/public/assets/development/themes/default.css @@ -0,0 +1,530 @@ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */ +/** + * Default SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2011-16, Sam Clarke + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +div.sceditor-grip, +.sceditor-button div { + background-image: url("famfamfam.png"); + background-repeat: no-repeat; + width: 16px; + height: 16px; +} +.sceditor-button-youtube div { + background-position: 0px 0px; +} +.sceditor-button-link div { + background-position: 0px -16px; +} +.sceditor-button-unlink div { + background-position: 0px -32px; +} +.sceditor-button-underline div { + background-position: 0px -48px; +} +.sceditor-button-time div { + background-position: 0px -64px; +} +.sceditor-button-table div { + background-position: 0px -80px; +} +.sceditor-button-superscript div { + background-position: 0px -96px; +} +.sceditor-button-subscript div { + background-position: 0px -112px; +} +.sceditor-button-strike div { + background-position: 0px -128px; +} +.sceditor-button-source div { + background-position: 0px -144px; +} +.sceditor-button-size div { + background-position: 0px -160px; +} +.sceditor-button-rtl div { + background-position: 0px -176px; +} +.sceditor-button-right div { + background-position: 0px -192px; +} +.sceditor-button-removeformat div { + background-position: 0px -208px; +} +.sceditor-button-quote div { + background-position: 0px -224px; +} +.sceditor-button-print div { + background-position: 0px -240px; +} +.sceditor-button-pastetext div { + background-position: 0px -256px; +} +.sceditor-button-paste div { + background-position: 0px -272px; +} +.sceditor-button-outdent div { + background-position: 0px -288px; +} +.sceditor-button-orderedlist div { + background-position: 0px -304px; +} +.sceditor-button-maximize div { + background-position: 0px -320px; +} +.sceditor-button-ltr div { + background-position: 0px -336px; +} +.sceditor-button-left div { + background-position: 0px -352px; +} +.sceditor-button-justify div { + background-position: 0px -368px; +} +.sceditor-button-italic div { + background-position: 0px -384px; +} +.sceditor-button-indent div { + background-position: 0px -400px; +} +.sceditor-button-image div { + background-position: 0px -416px; +} +.sceditor-button-horizontalrule div { + background-position: 0px -432px; +} +.sceditor-button-format div { + background-position: 0px -448px; +} +.sceditor-button-font div { + background-position: 0px -464px; +} +.sceditor-button-emoticon div { + background-position: 0px -480px; +} +.sceditor-button-email div { + background-position: 0px -496px; +} +.sceditor-button-date div { + background-position: 0px -512px; +} +.sceditor-button-cut div { + background-position: 0px -528px; +} +.sceditor-button-copy div { + background-position: 0px -544px; +} +.sceditor-button-color div { + background-position: 0px -560px; +} +.sceditor-button-code div { + background-position: 0px -576px; +} +.sceditor-button-center div { + background-position: 0px -592px; +} +.sceditor-button-bulletlist div { + background-position: 0px -608px; +} +.sceditor-button-bold div { + background-position: 0px -624px; +} +div.sceditor-grip { + background-position: 0px -640px; + width: 10px; + height: 10px; +} +.rtl div.sceditor-grip { + background-position: 0px -650px; +} +/** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +/*--------------------------------------------------- + LESS Elements 0.7 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +.sceditor-container { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + background: #fff; + border: 1px solid #d9d9d9; + font-size: 13px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + color: #333; + line-height: 1; + font-weight: bold; + height: 250px; + border-radius: 4px; + background-clip: padding-box; +} +.sceditor-container *, +.sceditor-container *:before, +.sceditor-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.sceditor-container, +.sceditor-container div, +div.sceditor-dropdown, +div.sceditor-dropdown div { + padding: 0; + margin: 0; + z-index: 3; +} +.sceditor-container iframe, +.sceditor-container textarea { + display: block; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + line-height: 1.25; + border: 0; + outline: none; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + padding: 0; + margin: 5px; + resize: none; + background: #fff; + height: auto !important; + width: auto !important; + width: calc(100% - 10px) !important; + min-height: 1px; +} +.sceditor-container textarea { + margin: 7px 5px; +} +div.sceditor-dnd-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: 5px dashed #aaa; + z-index: 200; + font-size: 2em; + text-align: center; + color: #aaa; +} +div.sceditor-dnd-cover p { + position: relative; + top: 45%; + pointer-events: none; +} +div.sceditor-resize-cover { + position: absolute; + top: 0; + left: 0; + background: #000; + width: 100%; + height: 100%; + z-index: 10; + opacity: 0.3; +} +div.sceditor-grip { + overflow: hidden; + width: 10px; + height: 10px; + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + z-index: 3; + line-height: 0; +} +div.sceditor-grip.has-icon { + background-image: none; +} +.sceditor-maximize { + position: fixed; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; + border-radius: 0; + background-clip: padding-box; + z-index: 2000; +} +html.sceditor-maximize, +body.sceditor-maximize { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +.sceditor-maximize div.sceditor-grip { + display: none; +} +.sceditor-maximize div.sceditor-toolbar { + border-radius: 0; + background-clip: padding-box; +} +/** + * Dropdown styleing + */ +div.sceditor-dropdown { + position: absolute; + border: 1px solid #ccc; + background: #fff; + z-index: 4000; + padding: 10px; + font-weight: normal; + font-size: 15px; + border-radius: 2px; + background-clip: padding-box; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2); +} +div.sceditor-dropdown *, +div.sceditor-dropdown *:before, +div.sceditor-dropdown *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +div.sceditor-dropdown a, +div.sceditor-dropdown a:link { + color: #333; +} +div.sceditor-dropdown form { + margin: 0; +} +div.sceditor-dropdown label { + display: block; + font-weight: bold; + color: #3c3c3c; + padding: 4px 0; +} +div.sceditor-dropdown input, +div.sceditor-dropdown textarea { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + outline: 0; + padding: 4px; + border: 1px solid #ccc; + border-top-color: #888; + margin: 0 0 .75em; + border-radius: 1px; + background-clip: padding-box; +} +div.sceditor-dropdown textarea { + padding: 6px; +} +div.sceditor-dropdown input:focus, +div.sceditor-dropdown textarea:focus { + border-color: #aaa; + border-top-color: #666; + box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1); +} +div.sceditor-dropdown .button { + font-weight: bold; + color: #444; + padding: 6px 12px; + background: #ececec; + border: solid 1px #ccc; + border-radius: 2px; + background-clip: padding-box; + cursor: pointer; + margin: .3em 0 0; +} +div.sceditor-dropdown .button:hover { + background: #f3f3f3; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); +} +div.sceditor-font-picker, +div.sceditor-fontsize-picker, +div.sceditor-format { + padding: 6px 0; +} +div.sceditor-color-picker { + padding: 4px; +} +div.sceditor-emoticons, +div.sceditor-more-emoticons { + padding: 0; +} +.sceditor-pastetext textarea { + border: 1px solid #bbb; + width: 20em; +} +.sceditor-emoticons img, +.sceditor-more-emoticons img { + padding: 0; + cursor: pointer; + margin: 2px; +} +.sceditor-more { + border-top: 1px solid #bbb; + display: block; + text-align: center; + cursor: pointer; + font-weight: bold; + padding: 6px 0; +} +.sceditor-dropdown a:hover { + background: #eee; +} +.sceditor-fontsize-option, +.sceditor-font-option, +.sceditor-format a { + display: block; + padding: 7px 10px; + cursor: pointer; + text-decoration: none; + color: #222; +} +.sceditor-fontsize-option { + padding: 7px 13px; +} +.sceditor-color-column { + float: left; +} +.sceditor-color-option { + display: block; + border: 2px solid #fff; + height: 18px; + width: 18px; + overflow: hidden; +} +.sceditor-color-option:hover { + border: 1px solid #aaa; +} +/** + * Toolbar styleing + */ +div.sceditor-toolbar { + flex-shrink: 0; + overflow: hidden; + padding: 3px 5px 2px; + background: #f7f7f7; + border-bottom: 1px solid #c0c0c0; + line-height: 0; + text-align: left; + user-select: none; + border-radius: 3px 3px 0 0; + background-clip: padding-box; +} +div.sceditor-group { + display: inline-block; + background: #ddd; + margin: 1px 5px 1px 0; + padding: 1px; + border-bottom: 1px solid #aaa; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button { + float: left; + cursor: pointer; + padding: 3px 5px; + width: 16px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2); +} +.sceditor-button:active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3); +} +.sceditor-button.disabled:hover { + background: inherit; + cursor: default; + box-shadow: none; +} +.sceditor-button, +.sceditor-button div { + display: block; +} +.sceditor-button svg { + display: inline-block; + height: 16px; + width: 16px; + margin: 2px 0; + fill: #111; + text-decoration: none; + pointer-events: none; + line-height: 1; +} +.sceditor-button.disabled svg { + fill: #888; +} +.sceditor-button div { + display: inline-block; + margin: 2px 0; + padding: 0; + overflow: hidden; + line-height: 0; + font-size: 0; + color: transparent; +} +.sceditor-button.has-icon div { + display: none; +} +.sceditor-button.disabled div { + opacity: 0.3; +} +.text .sceditor-button, +.text .sceditor-button div, +.sceditor-button.text, +.sceditor-button.text div, +.text-icon .sceditor-button, +.text-icon .sceditor-button div, +.sceditor-button.text-icon, +.sceditor-button.text-icon div { + display: inline-block; + width: auto; + line-height: 16px; + font-size: 1em; + color: inherit; + text-indent: 0; +} +.text-icon .sceditor-button.has-icon div, +.sceditor-button.has-icon div, +.text .sceditor-button div, +.sceditor-button.text div { + padding: 0 2px; + background: none; +} +.text .sceditor-button svg, +.sceditor-button.text svg { + display: none; +} +.text-icon .sceditor-button div, +.sceditor-button.text-icon div { + padding: 0 2px 0 20px; +} +.rtl div.sceditor-toolbar { + text-align: right; +} +.rtl .sceditor-button { + float: right; +} +.rtl div.sceditor-grip { + right: auto; + left: 0; +} diff --git a/public/assets/development/themes/defaultdark.css b/public/assets/development/themes/defaultdark.css new file mode 100644 index 0000000..09da75d --- /dev/null +++ b/public/assets/development/themes/defaultdark.css @@ -0,0 +1,548 @@ +/*! SCEditor | (C) 2017, Sam Clarke | sceditor.com/license */ +/** + * Default SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +div.sceditor-grip, +.sceditor-button div { + background-image: url("famfamfam.png"); + background-repeat: no-repeat; + width: 16px; + height: 16px; +} +.sceditor-button-youtube div { + background-position: 0px 0px; +} +.sceditor-button-link div { + background-position: 0px -16px; +} +.sceditor-button-unlink div { + background-position: 0px -32px; +} +.sceditor-button-underline div { + background-position: 0px -48px; +} +.sceditor-button-time div { + background-position: 0px -64px; +} +.sceditor-button-table div { + background-position: 0px -80px; +} +.sceditor-button-superscript div { + background-position: 0px -96px; +} +.sceditor-button-subscript div { + background-position: 0px -112px; +} +.sceditor-button-strike div { + background-position: 0px -128px; +} +.sceditor-button-source div { + background-position: 0px -144px; +} +.sceditor-button-size div { + background-position: 0px -160px; +} +.sceditor-button-rtl div { + background-position: 0px -176px; +} +.sceditor-button-right div { + background-position: 0px -192px; +} +.sceditor-button-removeformat div { + background-position: 0px -208px; +} +.sceditor-button-quote div { + background-position: 0px -224px; +} +.sceditor-button-print div { + background-position: 0px -240px; +} +.sceditor-button-pastetext div { + background-position: 0px -256px; +} +.sceditor-button-paste div { + background-position: 0px -272px; +} +.sceditor-button-outdent div { + background-position: 0px -288px; +} +.sceditor-button-orderedlist div { + background-position: 0px -304px; +} +.sceditor-button-maximize div { + background-position: 0px -320px; +} +.sceditor-button-ltr div { + background-position: 0px -336px; +} +.sceditor-button-left div { + background-position: 0px -352px; +} +.sceditor-button-justify div { + background-position: 0px -368px; +} +.sceditor-button-italic div { + background-position: 0px -384px; +} +.sceditor-button-indent div { + background-position: 0px -400px; +} +.sceditor-button-image div { + background-position: 0px -416px; +} +.sceditor-button-horizontalrule div { + background-position: 0px -432px; +} +.sceditor-button-format div { + background-position: 0px -448px; +} +.sceditor-button-font div { + background-position: 0px -464px; +} +.sceditor-button-emoticon div { + background-position: 0px -480px; +} +.sceditor-button-email div { + background-position: 0px -496px; +} +.sceditor-button-date div { + background-position: 0px -512px; +} +.sceditor-button-cut div { + background-position: 0px -528px; +} +.sceditor-button-copy div { + background-position: 0px -544px; +} +.sceditor-button-color div { + background-position: 0px -560px; +} +.sceditor-button-code div { + background-position: 0px -576px; +} +.sceditor-button-center div { + background-position: 0px -592px; +} +.sceditor-button-bulletlist div { + background-position: 0px -608px; +} +.sceditor-button-bold div { + background-position: 0px -624px; +} +div.sceditor-grip { + background-position: 0px -640px; + width: 10px; + height: 10px; +} +.rtl div.sceditor-grip { + background-position: 0px -650px; +} +/** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +/*--------------------------------------------------- + LESS Elements 0.7 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +.sceditor-container { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + background: #fff; + border: 1px solid #d9d9d9; + font-size: 13px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + color: #333; + line-height: 1; + font-weight: bold; + height: 250px; + border-radius: 4px; + background-clip: padding-box; +} +.sceditor-container *, +.sceditor-container *:before, +.sceditor-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.sceditor-container, +.sceditor-container div, +div.sceditor-dropdown, +div.sceditor-dropdown div { + padding: 0; + margin: 0; + z-index: 3; +} +.sceditor-container iframe, +.sceditor-container textarea { + display: block; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + line-height: 1.25; + border: 0; + outline: none; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + padding: 0; + margin: 5px; + resize: none; + background: #fff; + height: auto !important; + width: auto !important; + width: calc(100% - 10px) !important; + min-height: 1px; +} +.sceditor-container textarea { + margin: 7px 5px; +} +div.sceditor-dnd-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: 5px dashed #aaa; + z-index: 200; + font-size: 2em; + text-align: center; + color: #aaa; +} +div.sceditor-dnd-cover p { + position: relative; + top: 45%; + pointer-events: none; +} +div.sceditor-resize-cover { + position: absolute; + top: 0; + left: 0; + background: #000; + width: 100%; + height: 100%; + z-index: 10; + opacity: 0.3; +} +div.sceditor-grip { + overflow: hidden; + width: 10px; + height: 10px; + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + z-index: 3; + line-height: 0; +} +div.sceditor-grip.has-icon { + background-image: none; +} +.sceditor-maximize { + position: fixed; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; + border-radius: 0; + background-clip: padding-box; + z-index: 2000; +} +html.sceditor-maximize, +body.sceditor-maximize { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +.sceditor-maximize div.sceditor-grip { + display: none; +} +.sceditor-maximize div.sceditor-toolbar { + border-radius: 0; + background-clip: padding-box; +} +/** + * Dropdown styleing + */ +div.sceditor-dropdown { + position: absolute; + border: 1px solid #ccc; + background: #fff; + z-index: 4000; + padding: 10px; + font-weight: normal; + font-size: 15px; + border-radius: 2px; + background-clip: padding-box; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2); +} +div.sceditor-dropdown *, +div.sceditor-dropdown *:before, +div.sceditor-dropdown *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +div.sceditor-dropdown a, +div.sceditor-dropdown a:link { + color: #333; +} +div.sceditor-dropdown form { + margin: 0; +} +div.sceditor-dropdown label { + display: block; + font-weight: bold; + color: #3c3c3c; + padding: 4px 0; +} +div.sceditor-dropdown input, +div.sceditor-dropdown textarea { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + outline: 0; + padding: 4px; + border: 1px solid #ccc; + border-top-color: #888; + margin: 0 0 .75em; + border-radius: 1px; + background-clip: padding-box; +} +div.sceditor-dropdown textarea { + padding: 6px; +} +div.sceditor-dropdown input:focus, +div.sceditor-dropdown textarea:focus { + border-color: #aaa; + border-top-color: #666; + box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1); +} +div.sceditor-dropdown .button { + font-weight: bold; + color: #444; + padding: 6px 12px; + background: #ececec; + border: solid 1px #ccc; + border-radius: 2px; + background-clip: padding-box; + cursor: pointer; + margin: .3em 0 0; +} +div.sceditor-dropdown .button:hover { + background: #f3f3f3; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); +} +div.sceditor-font-picker, +div.sceditor-fontsize-picker, +div.sceditor-format { + padding: 6px 0; +} +div.sceditor-color-picker { + padding: 4px; +} +div.sceditor-emoticons, +div.sceditor-more-emoticons { + padding: 0; +} +.sceditor-pastetext textarea { + border: 1px solid #bbb; + width: 20em; +} +.sceditor-emoticons img, +.sceditor-more-emoticons img { + padding: 0; + cursor: pointer; + margin: 2px; +} +.sceditor-more { + border-top: 1px solid #bbb; + display: block; + text-align: center; + cursor: pointer; + font-weight: bold; + padding: 6px 0; +} +.sceditor-dropdown a:hover { + background: #eee; +} +.sceditor-fontsize-option, +.sceditor-font-option, +.sceditor-format a { + display: block; + padding: 7px 10px; + cursor: pointer; + text-decoration: none; + color: #222; +} +.sceditor-fontsize-option { + padding: 7px 13px; +} +.sceditor-color-column { + float: left; +} +.sceditor-color-option { + display: block; + border: 2px solid #fff; + height: 18px; + width: 18px; + overflow: hidden; +} +.sceditor-color-option:hover { + border: 1px solid #aaa; +} +/** + * Toolbar styleing + */ +div.sceditor-toolbar { + flex-shrink: 0; + overflow: hidden; + padding: 3px 5px 2px; + background: #f7f7f7; + border-bottom: 1px solid #c0c0c0; + line-height: 0; + text-align: left; + user-select: none; + border-radius: 3px 3px 0 0; + background-clip: padding-box; +} +div.sceditor-group { + display: inline-block; + background: #ddd; + margin: 1px 5px 1px 0; + padding: 1px; + border-bottom: 1px solid #aaa; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button { + float: left; + cursor: pointer; + padding: 3px 5px; + width: 16px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2); +} +.sceditor-button:active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3); +} +.sceditor-button.disabled:hover { + background: inherit; + cursor: default; + box-shadow: none; +} +.sceditor-button, +.sceditor-button div { + display: block; +} +.sceditor-button svg { + display: inline-block; + height: 16px; + width: 16px; + margin: 2px 0; + fill: #111; + text-decoration: none; + pointer-events: none; + line-height: 1; +} +.sceditor-button.disabled svg { + fill: #888; +} +.sceditor-button div { + display: inline-block; + margin: 2px 0; + padding: 0; + overflow: hidden; + line-height: 0; + font-size: 0; + color: transparent; +} +.sceditor-button.has-icon div { + display: none; +} +.sceditor-button.disabled div { + opacity: 0.3; +} +.text .sceditor-button, +.text .sceditor-button div, +.sceditor-button.text, +.sceditor-button.text div, +.text-icon .sceditor-button, +.text-icon .sceditor-button div, +.sceditor-button.text-icon, +.sceditor-button.text-icon div { + display: inline-block; + width: auto; + line-height: 16px; + font-size: 1em; + color: inherit; + text-indent: 0; +} +.text-icon .sceditor-button.has-icon div, +.sceditor-button.has-icon div, +.text .sceditor-button div, +.sceditor-button.text div { + padding: 0 2px; + background: none; +} +.text .sceditor-button svg, +.sceditor-button.text svg { + display: none; +} +.text-icon .sceditor-button div, +.sceditor-button.text-icon div { + padding: 0 2px 0 20px; +} +.rtl div.sceditor-toolbar { + text-align: right; +} +.rtl .sceditor-button { + float: right; +} +.rtl div.sceditor-grip { + right: auto; + left: 0; +} +div.sceditor-toolbar { + background: #5d5d5d; +} +div.sceditor-group { + background: #303030; + border-bottom: 1px solid #000; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #6b6b6b; +} +.sceditor-button svg { + fill: #fff; +} +.sceditor-button.disabled svg { + fill: #777; +} diff --git a/public/assets/development/themes/famfamfam.png b/public/assets/development/themes/famfamfam.png new file mode 100644 index 0000000000000000000000000000000000000000..488c72aca95abf57bd15d3544696eddaa4a60750 GIT binary patch literal 4583 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 R^t1o~002ovPDHLkV1hp-8@>Pl literal 0 HcmV?d00001 diff --git a/public/assets/development/themes/modern.css b/public/assets/development/themes/modern.css new file mode 100644 index 0000000..e3c4a67 --- /dev/null +++ b/public/assets/development/themes/modern.css @@ -0,0 +1,604 @@ +/** + * Modern theme + * + * Copyright (C) 2012, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/) + * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/) + */ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */ +/** + * Default SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2011-16, Sam Clarke + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +div.sceditor-grip, +.sceditor-button div { + background-image: url("famfamfam.png"); + background-repeat: no-repeat; + width: 16px; + height: 16px; +} +.sceditor-button-youtube div { + background-position: 0px 0px; +} +.sceditor-button-link div { + background-position: 0px -16px; +} +.sceditor-button-unlink div { + background-position: 0px -32px; +} +.sceditor-button-underline div { + background-position: 0px -48px; +} +.sceditor-button-time div { + background-position: 0px -64px; +} +.sceditor-button-table div { + background-position: 0px -80px; +} +.sceditor-button-superscript div { + background-position: 0px -96px; +} +.sceditor-button-subscript div { + background-position: 0px -112px; +} +.sceditor-button-strike div { + background-position: 0px -128px; +} +.sceditor-button-source div { + background-position: 0px -144px; +} +.sceditor-button-size div { + background-position: 0px -160px; +} +.sceditor-button-rtl div { + background-position: 0px -176px; +} +.sceditor-button-right div { + background-position: 0px -192px; +} +.sceditor-button-removeformat div { + background-position: 0px -208px; +} +.sceditor-button-quote div { + background-position: 0px -224px; +} +.sceditor-button-print div { + background-position: 0px -240px; +} +.sceditor-button-pastetext div { + background-position: 0px -256px; +} +.sceditor-button-paste div { + background-position: 0px -272px; +} +.sceditor-button-outdent div { + background-position: 0px -288px; +} +.sceditor-button-orderedlist div { + background-position: 0px -304px; +} +.sceditor-button-maximize div { + background-position: 0px -320px; +} +.sceditor-button-ltr div { + background-position: 0px -336px; +} +.sceditor-button-left div { + background-position: 0px -352px; +} +.sceditor-button-justify div { + background-position: 0px -368px; +} +.sceditor-button-italic div { + background-position: 0px -384px; +} +.sceditor-button-indent div { + background-position: 0px -400px; +} +.sceditor-button-image div { + background-position: 0px -416px; +} +.sceditor-button-horizontalrule div { + background-position: 0px -432px; +} +.sceditor-button-format div { + background-position: 0px -448px; +} +.sceditor-button-font div { + background-position: 0px -464px; +} +.sceditor-button-emoticon div { + background-position: 0px -480px; +} +.sceditor-button-email div { + background-position: 0px -496px; +} +.sceditor-button-date div { + background-position: 0px -512px; +} +.sceditor-button-cut div { + background-position: 0px -528px; +} +.sceditor-button-copy div { + background-position: 0px -544px; +} +.sceditor-button-color div { + background-position: 0px -560px; +} +.sceditor-button-code div { + background-position: 0px -576px; +} +.sceditor-button-center div { + background-position: 0px -592px; +} +.sceditor-button-bulletlist div { + background-position: 0px -608px; +} +.sceditor-button-bold div { + background-position: 0px -624px; +} +div.sceditor-grip { + background-position: 0px -640px; + width: 10px; + height: 10px; +} +.rtl div.sceditor-grip { + background-position: 0px -650px; +} +/** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +/*--------------------------------------------------- + LESS Elements 0.7 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +.sceditor-container { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + background: #fff; + border: 1px solid #d9d9d9; + font-size: 13px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + color: #333; + line-height: 1; + font-weight: bold; + height: 250px; + border-radius: 4px; + background-clip: padding-box; +} +.sceditor-container *, +.sceditor-container *:before, +.sceditor-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.sceditor-container, +.sceditor-container div, +div.sceditor-dropdown, +div.sceditor-dropdown div { + padding: 0; + margin: 0; + z-index: 3; +} +.sceditor-container iframe, +.sceditor-container textarea { + display: block; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + line-height: 1.25; + border: 0; + outline: none; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + padding: 0; + margin: 5px; + resize: none; + background: #fff; + height: auto !important; + width: auto !important; + width: calc(100% - 10px) !important; + min-height: 1px; +} +.sceditor-container textarea { + margin: 7px 5px; +} +div.sceditor-dnd-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: 5px dashed #aaa; + z-index: 200; + font-size: 2em; + text-align: center; + color: #aaa; +} +div.sceditor-dnd-cover p { + position: relative; + top: 45%; + pointer-events: none; +} +div.sceditor-resize-cover { + position: absolute; + top: 0; + left: 0; + background: #000; + width: 100%; + height: 100%; + z-index: 10; + opacity: 0.3; +} +div.sceditor-grip { + overflow: hidden; + width: 10px; + height: 10px; + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + z-index: 3; + line-height: 0; +} +div.sceditor-grip.has-icon { + background-image: none; +} +.sceditor-maximize { + position: fixed; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; + border-radius: 0; + background-clip: padding-box; + z-index: 2000; +} +html.sceditor-maximize, +body.sceditor-maximize { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +.sceditor-maximize div.sceditor-grip { + display: none; +} +.sceditor-maximize div.sceditor-toolbar { + border-radius: 0; + background-clip: padding-box; +} +/** + * Dropdown styleing + */ +div.sceditor-dropdown { + position: absolute; + border: 1px solid #ccc; + background: #fff; + z-index: 4000; + padding: 10px; + font-weight: normal; + font-size: 15px; + border-radius: 2px; + background-clip: padding-box; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2); +} +div.sceditor-dropdown *, +div.sceditor-dropdown *:before, +div.sceditor-dropdown *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +div.sceditor-dropdown a, +div.sceditor-dropdown a:link { + color: #333; +} +div.sceditor-dropdown form { + margin: 0; +} +div.sceditor-dropdown label { + display: block; + font-weight: bold; + color: #3c3c3c; + padding: 4px 0; +} +div.sceditor-dropdown input, +div.sceditor-dropdown textarea { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + outline: 0; + padding: 4px; + border: 1px solid #ccc; + border-top-color: #888; + margin: 0 0 .75em; + border-radius: 1px; + background-clip: padding-box; +} +div.sceditor-dropdown textarea { + padding: 6px; +} +div.sceditor-dropdown input:focus, +div.sceditor-dropdown textarea:focus { + border-color: #aaa; + border-top-color: #666; + box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1); +} +div.sceditor-dropdown .button { + font-weight: bold; + color: #444; + padding: 6px 12px; + background: #ececec; + border: solid 1px #ccc; + border-radius: 2px; + background-clip: padding-box; + cursor: pointer; + margin: .3em 0 0; +} +div.sceditor-dropdown .button:hover { + background: #f3f3f3; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); +} +div.sceditor-font-picker, +div.sceditor-fontsize-picker, +div.sceditor-format { + padding: 6px 0; +} +div.sceditor-color-picker { + padding: 4px; +} +div.sceditor-emoticons, +div.sceditor-more-emoticons { + padding: 0; +} +.sceditor-pastetext textarea { + border: 1px solid #bbb; + width: 20em; +} +.sceditor-emoticons img, +.sceditor-more-emoticons img { + padding: 0; + cursor: pointer; + margin: 2px; +} +.sceditor-more { + border-top: 1px solid #bbb; + display: block; + text-align: center; + cursor: pointer; + font-weight: bold; + padding: 6px 0; +} +.sceditor-dropdown a:hover { + background: #eee; +} +.sceditor-fontsize-option, +.sceditor-font-option, +.sceditor-format a { + display: block; + padding: 7px 10px; + cursor: pointer; + text-decoration: none; + color: #222; +} +.sceditor-fontsize-option { + padding: 7px 13px; +} +.sceditor-color-column { + float: left; +} +.sceditor-color-option { + display: block; + border: 2px solid #fff; + height: 18px; + width: 18px; + overflow: hidden; +} +.sceditor-color-option:hover { + border: 1px solid #aaa; +} +/** + * Toolbar styleing + */ +div.sceditor-toolbar { + flex-shrink: 0; + overflow: hidden; + padding: 3px 5px 2px; + background: #f7f7f7; + border-bottom: 1px solid #c0c0c0; + line-height: 0; + text-align: left; + user-select: none; + border-radius: 3px 3px 0 0; + background-clip: padding-box; +} +div.sceditor-group { + display: inline-block; + background: #ddd; + margin: 1px 5px 1px 0; + padding: 1px; + border-bottom: 1px solid #aaa; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button { + float: left; + cursor: pointer; + padding: 3px 5px; + width: 16px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2); +} +.sceditor-button:active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3); +} +.sceditor-button.disabled:hover { + background: inherit; + cursor: default; + box-shadow: none; +} +.sceditor-button, +.sceditor-button div { + display: block; +} +.sceditor-button svg { + display: inline-block; + height: 16px; + width: 16px; + margin: 2px 0; + fill: #111; + text-decoration: none; + pointer-events: none; + line-height: 1; +} +.sceditor-button.disabled svg { + fill: #888; +} +.sceditor-button div { + display: inline-block; + margin: 2px 0; + padding: 0; + overflow: hidden; + line-height: 0; + font-size: 0; + color: transparent; +} +.sceditor-button.has-icon div { + display: none; +} +.sceditor-button.disabled div { + opacity: 0.3; +} +.text .sceditor-button, +.text .sceditor-button div, +.sceditor-button.text, +.sceditor-button.text div, +.text-icon .sceditor-button, +.text-icon .sceditor-button div, +.sceditor-button.text-icon, +.sceditor-button.text-icon div { + display: inline-block; + width: auto; + line-height: 16px; + font-size: 1em; + color: inherit; + text-indent: 0; +} +.text-icon .sceditor-button.has-icon div, +.sceditor-button.has-icon div, +.text .sceditor-button div, +.sceditor-button.text div { + padding: 0 2px; + background: none; +} +.text .sceditor-button svg, +.sceditor-button.text svg { + display: none; +} +.text-icon .sceditor-button div, +.sceditor-button.text-icon div { + padding: 0 2px 0 20px; +} +.rtl div.sceditor-toolbar { + text-align: right; +} +.rtl .sceditor-button { + float: right; +} +.rtl div.sceditor-grip { + right: auto; + left: 0; +} +.sceditor-container { + border: 1px solid #999; +} +.sceditor-container textarea { + font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace; + background: #2e3436; + color: #fff; + margin: 0; + padding: 5px; +} +div.sceditor-toolbar { + background: #ccc; + background: linear-gradient(to bottom, #cccccc 0%, #b2b2b2 100%); +} +div.sceditor-group { + display: inline; + background: transparent; + margin: 0; + padding: 0; + border: 0; +} +.sceditor-button { + padding: 4px; + margin: 2px 1px 2px 3px; + height: 16px; + border-radius: 12px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button.active, +.sceditor-button.active:hover { + box-shadow: none; +} +.sceditor-button:hover { + background: #fff; + background: rgba(255, 255, 255, 0.75); + margin: 1px 0 1px 2px; + border: 1px solid #eee; +} +.sceditor-button.disabled:hover { + margin: 2px 1px 2px 3px; + border: 0; +} +.sceditor-button.active { + background: #b1b1b1; + background: rgba(0, 0, 0, 0.1); + margin: 1px 0 1px 2px; + border: 1px solid #999; +} +.sceditor-button.active:hover { + background: #fff; + background: rgba(255, 255, 255, 0.25); +} +.sceditor-button:active, +.sceditor-button.active:active { + margin: 1px 0 1px 2px; + border: 1px solid #999; + box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.5); +} +.sceditor-button div, +.sceditor-button svg { + margin: 0; +} diff --git a/public/assets/development/themes/office-toolbar.css b/public/assets/development/themes/office-toolbar.css new file mode 100644 index 0000000..ed3ee3c --- /dev/null +++ b/public/assets/development/themes/office-toolbar.css @@ -0,0 +1,596 @@ +/** + * Copyright (C) 2012, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/) + * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/) + */ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */ +/** + * Default SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2011-16, Sam Clarke + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +div.sceditor-grip, +.sceditor-button div { + background-image: url("famfamfam.png"); + background-repeat: no-repeat; + width: 16px; + height: 16px; +} +.sceditor-button-youtube div { + background-position: 0px 0px; +} +.sceditor-button-link div { + background-position: 0px -16px; +} +.sceditor-button-unlink div { + background-position: 0px -32px; +} +.sceditor-button-underline div { + background-position: 0px -48px; +} +.sceditor-button-time div { + background-position: 0px -64px; +} +.sceditor-button-table div { + background-position: 0px -80px; +} +.sceditor-button-superscript div { + background-position: 0px -96px; +} +.sceditor-button-subscript div { + background-position: 0px -112px; +} +.sceditor-button-strike div { + background-position: 0px -128px; +} +.sceditor-button-source div { + background-position: 0px -144px; +} +.sceditor-button-size div { + background-position: 0px -160px; +} +.sceditor-button-rtl div { + background-position: 0px -176px; +} +.sceditor-button-right div { + background-position: 0px -192px; +} +.sceditor-button-removeformat div { + background-position: 0px -208px; +} +.sceditor-button-quote div { + background-position: 0px -224px; +} +.sceditor-button-print div { + background-position: 0px -240px; +} +.sceditor-button-pastetext div { + background-position: 0px -256px; +} +.sceditor-button-paste div { + background-position: 0px -272px; +} +.sceditor-button-outdent div { + background-position: 0px -288px; +} +.sceditor-button-orderedlist div { + background-position: 0px -304px; +} +.sceditor-button-maximize div { + background-position: 0px -320px; +} +.sceditor-button-ltr div { + background-position: 0px -336px; +} +.sceditor-button-left div { + background-position: 0px -352px; +} +.sceditor-button-justify div { + background-position: 0px -368px; +} +.sceditor-button-italic div { + background-position: 0px -384px; +} +.sceditor-button-indent div { + background-position: 0px -400px; +} +.sceditor-button-image div { + background-position: 0px -416px; +} +.sceditor-button-horizontalrule div { + background-position: 0px -432px; +} +.sceditor-button-format div { + background-position: 0px -448px; +} +.sceditor-button-font div { + background-position: 0px -464px; +} +.sceditor-button-emoticon div { + background-position: 0px -480px; +} +.sceditor-button-email div { + background-position: 0px -496px; +} +.sceditor-button-date div { + background-position: 0px -512px; +} +.sceditor-button-cut div { + background-position: 0px -528px; +} +.sceditor-button-copy div { + background-position: 0px -544px; +} +.sceditor-button-color div { + background-position: 0px -560px; +} +.sceditor-button-code div { + background-position: 0px -576px; +} +.sceditor-button-center div { + background-position: 0px -592px; +} +.sceditor-button-bulletlist div { + background-position: 0px -608px; +} +.sceditor-button-bold div { + background-position: 0px -624px; +} +div.sceditor-grip { + background-position: 0px -640px; + width: 10px; + height: 10px; +} +.rtl div.sceditor-grip { + background-position: 0px -650px; +} +/** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +/*--------------------------------------------------- + LESS Elements 0.7 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +.sceditor-container { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + background: #fff; + border: 1px solid #d9d9d9; + font-size: 13px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + color: #333; + line-height: 1; + font-weight: bold; + height: 250px; + border-radius: 4px; + background-clip: padding-box; +} +.sceditor-container *, +.sceditor-container *:before, +.sceditor-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.sceditor-container, +.sceditor-container div, +div.sceditor-dropdown, +div.sceditor-dropdown div { + padding: 0; + margin: 0; + z-index: 3; +} +.sceditor-container iframe, +.sceditor-container textarea { + display: block; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + line-height: 1.25; + border: 0; + outline: none; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + padding: 0; + margin: 5px; + resize: none; + background: #fff; + height: auto !important; + width: auto !important; + width: calc(100% - 10px) !important; + min-height: 1px; +} +.sceditor-container textarea { + margin: 7px 5px; +} +div.sceditor-dnd-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: 5px dashed #aaa; + z-index: 200; + font-size: 2em; + text-align: center; + color: #aaa; +} +div.sceditor-dnd-cover p { + position: relative; + top: 45%; + pointer-events: none; +} +div.sceditor-resize-cover { + position: absolute; + top: 0; + left: 0; + background: #000; + width: 100%; + height: 100%; + z-index: 10; + opacity: 0.3; +} +div.sceditor-grip { + overflow: hidden; + width: 10px; + height: 10px; + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + z-index: 3; + line-height: 0; +} +div.sceditor-grip.has-icon { + background-image: none; +} +.sceditor-maximize { + position: fixed; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; + border-radius: 0; + background-clip: padding-box; + z-index: 2000; +} +html.sceditor-maximize, +body.sceditor-maximize { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +.sceditor-maximize div.sceditor-grip { + display: none; +} +.sceditor-maximize div.sceditor-toolbar { + border-radius: 0; + background-clip: padding-box; +} +/** + * Dropdown styleing + */ +div.sceditor-dropdown { + position: absolute; + border: 1px solid #ccc; + background: #fff; + z-index: 4000; + padding: 10px; + font-weight: normal; + font-size: 15px; + border-radius: 2px; + background-clip: padding-box; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2); +} +div.sceditor-dropdown *, +div.sceditor-dropdown *:before, +div.sceditor-dropdown *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +div.sceditor-dropdown a, +div.sceditor-dropdown a:link { + color: #333; +} +div.sceditor-dropdown form { + margin: 0; +} +div.sceditor-dropdown label { + display: block; + font-weight: bold; + color: #3c3c3c; + padding: 4px 0; +} +div.sceditor-dropdown input, +div.sceditor-dropdown textarea { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + outline: 0; + padding: 4px; + border: 1px solid #ccc; + border-top-color: #888; + margin: 0 0 .75em; + border-radius: 1px; + background-clip: padding-box; +} +div.sceditor-dropdown textarea { + padding: 6px; +} +div.sceditor-dropdown input:focus, +div.sceditor-dropdown textarea:focus { + border-color: #aaa; + border-top-color: #666; + box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1); +} +div.sceditor-dropdown .button { + font-weight: bold; + color: #444; + padding: 6px 12px; + background: #ececec; + border: solid 1px #ccc; + border-radius: 2px; + background-clip: padding-box; + cursor: pointer; + margin: .3em 0 0; +} +div.sceditor-dropdown .button:hover { + background: #f3f3f3; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); +} +div.sceditor-font-picker, +div.sceditor-fontsize-picker, +div.sceditor-format { + padding: 6px 0; +} +div.sceditor-color-picker { + padding: 4px; +} +div.sceditor-emoticons, +div.sceditor-more-emoticons { + padding: 0; +} +.sceditor-pastetext textarea { + border: 1px solid #bbb; + width: 20em; +} +.sceditor-emoticons img, +.sceditor-more-emoticons img { + padding: 0; + cursor: pointer; + margin: 2px; +} +.sceditor-more { + border-top: 1px solid #bbb; + display: block; + text-align: center; + cursor: pointer; + font-weight: bold; + padding: 6px 0; +} +.sceditor-dropdown a:hover { + background: #eee; +} +.sceditor-fontsize-option, +.sceditor-font-option, +.sceditor-format a { + display: block; + padding: 7px 10px; + cursor: pointer; + text-decoration: none; + color: #222; +} +.sceditor-fontsize-option { + padding: 7px 13px; +} +.sceditor-color-column { + float: left; +} +.sceditor-color-option { + display: block; + border: 2px solid #fff; + height: 18px; + width: 18px; + overflow: hidden; +} +.sceditor-color-option:hover { + border: 1px solid #aaa; +} +/** + * Toolbar styleing + */ +div.sceditor-toolbar { + flex-shrink: 0; + overflow: hidden; + padding: 3px 5px 2px; + background: #f7f7f7; + border-bottom: 1px solid #c0c0c0; + line-height: 0; + text-align: left; + user-select: none; + border-radius: 3px 3px 0 0; + background-clip: padding-box; +} +div.sceditor-group { + display: inline-block; + background: #ddd; + margin: 1px 5px 1px 0; + padding: 1px; + border-bottom: 1px solid #aaa; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button { + float: left; + cursor: pointer; + padding: 3px 5px; + width: 16px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2); +} +.sceditor-button:active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3); +} +.sceditor-button.disabled:hover { + background: inherit; + cursor: default; + box-shadow: none; +} +.sceditor-button, +.sceditor-button div { + display: block; +} +.sceditor-button svg { + display: inline-block; + height: 16px; + width: 16px; + margin: 2px 0; + fill: #111; + text-decoration: none; + pointer-events: none; + line-height: 1; +} +.sceditor-button.disabled svg { + fill: #888; +} +.sceditor-button div { + display: inline-block; + margin: 2px 0; + padding: 0; + overflow: hidden; + line-height: 0; + font-size: 0; + color: transparent; +} +.sceditor-button.has-icon div { + display: none; +} +.sceditor-button.disabled div { + opacity: 0.3; +} +.text .sceditor-button, +.text .sceditor-button div, +.sceditor-button.text, +.sceditor-button.text div, +.text-icon .sceditor-button, +.text-icon .sceditor-button div, +.sceditor-button.text-icon, +.sceditor-button.text-icon div { + display: inline-block; + width: auto; + line-height: 16px; + font-size: 1em; + color: inherit; + text-indent: 0; +} +.text-icon .sceditor-button.has-icon div, +.sceditor-button.has-icon div, +.text .sceditor-button div, +.sceditor-button.text div { + padding: 0 2px; + background: none; +} +.text .sceditor-button svg, +.sceditor-button.text svg { + display: none; +} +.text-icon .sceditor-button div, +.sceditor-button.text-icon div { + padding: 0 2px 0 20px; +} +.rtl div.sceditor-toolbar { + text-align: right; +} +.rtl .sceditor-button { + float: right; +} +.rtl div.sceditor-grip { + right: auto; + left: 0; +} +.sceditor-container { + border: 1px solid #8db2e3; +} +.sceditor-container textarea { + font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace; +} +div.sceditor-toolbar { + border-bottom: 1px solid #95a9c3; + background: #dee8f5; + background: linear-gradient(to bottom, #dee8f5 0%, #c7d8ed 29%, #ccdcee 61%, #c0d8ef 100%); +} +div.sceditor-group { + border: 1px solid #7596bf; + background: transparent; + padding: 0; + background: #cadcf0; + background: linear-gradient(to bottom, #cadcf0 24%, #bcd0e9 38%, #d0e1f7 99%); +} +.sceditor-button { + height: 16px; + padding: 3px 4px; + border-radius: 0; + background-clip: padding-box; + box-shadow: inset 0 1px #d5e3f1, inset 0 -1px #e3edfb, inset 1px 0 #cddcef, inset -1px 0 #b8ceea; +} +.sceditor-button:first-child { + border-radius: 4px 0 0 4px; + background-clip: padding-box; +} +.sceditor-button:last-child { + border-radius: 0 4px 4px 0; + background-clip: padding-box; +} +.sceditor-button div, +.sceditor-button svg { + margin: 0; +} +.sceditor-button.active { + background: #fbdbb5; + background: linear-gradient(to bottom, #fbdbb5 11%, #feb456 29%, #fdeb9f 99%); + box-shadow: inset 0 1px #ebd1b4, inset 0 -1px #ffe47f, inset -1px 0 #b8ceea; +} +.sceditor-button:hover { + background: #fef7d5; + background: linear-gradient(to bottom, #fef7d5 0%, #fae5a9 42%, #ffd048 42%, #ffe59f 100%); + box-shadow: inset 0 1px #fffbe8, inset -1px 0 #ffefc4, inset 0 -1px #fff9cc; +} +.sceditor-button:active { + background: #e7a66d; + background: linear-gradient(to bottom, #e7a66d 0%, #fcb16d 1%, #ff8d05 42%, #ffc450 100%); + box-shadow: inset 0 1px 1px #7b6645, inset 0 -1px #d19c33; +} +.sceditor-button.active:hover { + background: #dba368; + background: linear-gradient(to bottom, #dba368 0%, #ffbd79 4%, #fea335 34%, #ffc64c 66%, #fee069 100%); + box-shadow: inset 0 1px 1px #9e8255, inset 0 -1px #fcce6b; +} diff --git a/public/assets/development/themes/office.css b/public/assets/development/themes/office.css new file mode 100644 index 0000000..8cd4ba9 --- /dev/null +++ b/public/assets/development/themes/office.css @@ -0,0 +1,618 @@ +/** + * Copyright (C) 2012, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/) + * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/) + */ +/** + * Copyright (C) 2012, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/) + * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/) + */ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */ +/** + * Default SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2011-16, Sam Clarke + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +div.sceditor-grip, +.sceditor-button div { + background-image: url("famfamfam.png"); + background-repeat: no-repeat; + width: 16px; + height: 16px; +} +.sceditor-button-youtube div { + background-position: 0px 0px; +} +.sceditor-button-link div { + background-position: 0px -16px; +} +.sceditor-button-unlink div { + background-position: 0px -32px; +} +.sceditor-button-underline div { + background-position: 0px -48px; +} +.sceditor-button-time div { + background-position: 0px -64px; +} +.sceditor-button-table div { + background-position: 0px -80px; +} +.sceditor-button-superscript div { + background-position: 0px -96px; +} +.sceditor-button-subscript div { + background-position: 0px -112px; +} +.sceditor-button-strike div { + background-position: 0px -128px; +} +.sceditor-button-source div { + background-position: 0px -144px; +} +.sceditor-button-size div { + background-position: 0px -160px; +} +.sceditor-button-rtl div { + background-position: 0px -176px; +} +.sceditor-button-right div { + background-position: 0px -192px; +} +.sceditor-button-removeformat div { + background-position: 0px -208px; +} +.sceditor-button-quote div { + background-position: 0px -224px; +} +.sceditor-button-print div { + background-position: 0px -240px; +} +.sceditor-button-pastetext div { + background-position: 0px -256px; +} +.sceditor-button-paste div { + background-position: 0px -272px; +} +.sceditor-button-outdent div { + background-position: 0px -288px; +} +.sceditor-button-orderedlist div { + background-position: 0px -304px; +} +.sceditor-button-maximize div { + background-position: 0px -320px; +} +.sceditor-button-ltr div { + background-position: 0px -336px; +} +.sceditor-button-left div { + background-position: 0px -352px; +} +.sceditor-button-justify div { + background-position: 0px -368px; +} +.sceditor-button-italic div { + background-position: 0px -384px; +} +.sceditor-button-indent div { + background-position: 0px -400px; +} +.sceditor-button-image div { + background-position: 0px -416px; +} +.sceditor-button-horizontalrule div { + background-position: 0px -432px; +} +.sceditor-button-format div { + background-position: 0px -448px; +} +.sceditor-button-font div { + background-position: 0px -464px; +} +.sceditor-button-emoticon div { + background-position: 0px -480px; +} +.sceditor-button-email div { + background-position: 0px -496px; +} +.sceditor-button-date div { + background-position: 0px -512px; +} +.sceditor-button-cut div { + background-position: 0px -528px; +} +.sceditor-button-copy div { + background-position: 0px -544px; +} +.sceditor-button-color div { + background-position: 0px -560px; +} +.sceditor-button-code div { + background-position: 0px -576px; +} +.sceditor-button-center div { + background-position: 0px -592px; +} +.sceditor-button-bulletlist div { + background-position: 0px -608px; +} +.sceditor-button-bold div { + background-position: 0px -624px; +} +div.sceditor-grip { + background-position: 0px -640px; + width: 10px; + height: 10px; +} +.rtl div.sceditor-grip { + background-position: 0px -650px; +} +/** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +/*--------------------------------------------------- + LESS Elements 0.7 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +.sceditor-container { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + background: #fff; + border: 1px solid #d9d9d9; + font-size: 13px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + color: #333; + line-height: 1; + font-weight: bold; + height: 250px; + border-radius: 4px; + background-clip: padding-box; +} +.sceditor-container *, +.sceditor-container *:before, +.sceditor-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.sceditor-container, +.sceditor-container div, +div.sceditor-dropdown, +div.sceditor-dropdown div { + padding: 0; + margin: 0; + z-index: 3; +} +.sceditor-container iframe, +.sceditor-container textarea { + display: block; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + line-height: 1.25; + border: 0; + outline: none; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + padding: 0; + margin: 5px; + resize: none; + background: #fff; + height: auto !important; + width: auto !important; + width: calc(100% - 10px) !important; + min-height: 1px; +} +.sceditor-container textarea { + margin: 7px 5px; +} +div.sceditor-dnd-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: 5px dashed #aaa; + z-index: 200; + font-size: 2em; + text-align: center; + color: #aaa; +} +div.sceditor-dnd-cover p { + position: relative; + top: 45%; + pointer-events: none; +} +div.sceditor-resize-cover { + position: absolute; + top: 0; + left: 0; + background: #000; + width: 100%; + height: 100%; + z-index: 10; + opacity: 0.3; +} +div.sceditor-grip { + overflow: hidden; + width: 10px; + height: 10px; + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + z-index: 3; + line-height: 0; +} +div.sceditor-grip.has-icon { + background-image: none; +} +.sceditor-maximize { + position: fixed; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; + border-radius: 0; + background-clip: padding-box; + z-index: 2000; +} +html.sceditor-maximize, +body.sceditor-maximize { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +.sceditor-maximize div.sceditor-grip { + display: none; +} +.sceditor-maximize div.sceditor-toolbar { + border-radius: 0; + background-clip: padding-box; +} +/** + * Dropdown styleing + */ +div.sceditor-dropdown { + position: absolute; + border: 1px solid #ccc; + background: #fff; + z-index: 4000; + padding: 10px; + font-weight: normal; + font-size: 15px; + border-radius: 2px; + background-clip: padding-box; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2); +} +div.sceditor-dropdown *, +div.sceditor-dropdown *:before, +div.sceditor-dropdown *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +div.sceditor-dropdown a, +div.sceditor-dropdown a:link { + color: #333; +} +div.sceditor-dropdown form { + margin: 0; +} +div.sceditor-dropdown label { + display: block; + font-weight: bold; + color: #3c3c3c; + padding: 4px 0; +} +div.sceditor-dropdown input, +div.sceditor-dropdown textarea { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + outline: 0; + padding: 4px; + border: 1px solid #ccc; + border-top-color: #888; + margin: 0 0 .75em; + border-radius: 1px; + background-clip: padding-box; +} +div.sceditor-dropdown textarea { + padding: 6px; +} +div.sceditor-dropdown input:focus, +div.sceditor-dropdown textarea:focus { + border-color: #aaa; + border-top-color: #666; + box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1); +} +div.sceditor-dropdown .button { + font-weight: bold; + color: #444; + padding: 6px 12px; + background: #ececec; + border: solid 1px #ccc; + border-radius: 2px; + background-clip: padding-box; + cursor: pointer; + margin: .3em 0 0; +} +div.sceditor-dropdown .button:hover { + background: #f3f3f3; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); +} +div.sceditor-font-picker, +div.sceditor-fontsize-picker, +div.sceditor-format { + padding: 6px 0; +} +div.sceditor-color-picker { + padding: 4px; +} +div.sceditor-emoticons, +div.sceditor-more-emoticons { + padding: 0; +} +.sceditor-pastetext textarea { + border: 1px solid #bbb; + width: 20em; +} +.sceditor-emoticons img, +.sceditor-more-emoticons img { + padding: 0; + cursor: pointer; + margin: 2px; +} +.sceditor-more { + border-top: 1px solid #bbb; + display: block; + text-align: center; + cursor: pointer; + font-weight: bold; + padding: 6px 0; +} +.sceditor-dropdown a:hover { + background: #eee; +} +.sceditor-fontsize-option, +.sceditor-font-option, +.sceditor-format a { + display: block; + padding: 7px 10px; + cursor: pointer; + text-decoration: none; + color: #222; +} +.sceditor-fontsize-option { + padding: 7px 13px; +} +.sceditor-color-column { + float: left; +} +.sceditor-color-option { + display: block; + border: 2px solid #fff; + height: 18px; + width: 18px; + overflow: hidden; +} +.sceditor-color-option:hover { + border: 1px solid #aaa; +} +/** + * Toolbar styleing + */ +div.sceditor-toolbar { + flex-shrink: 0; + overflow: hidden; + padding: 3px 5px 2px; + background: #f7f7f7; + border-bottom: 1px solid #c0c0c0; + line-height: 0; + text-align: left; + user-select: none; + border-radius: 3px 3px 0 0; + background-clip: padding-box; +} +div.sceditor-group { + display: inline-block; + background: #ddd; + margin: 1px 5px 1px 0; + padding: 1px; + border-bottom: 1px solid #aaa; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button { + float: left; + cursor: pointer; + padding: 3px 5px; + width: 16px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2); +} +.sceditor-button:active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3); +} +.sceditor-button.disabled:hover { + background: inherit; + cursor: default; + box-shadow: none; +} +.sceditor-button, +.sceditor-button div { + display: block; +} +.sceditor-button svg { + display: inline-block; + height: 16px; + width: 16px; + margin: 2px 0; + fill: #111; + text-decoration: none; + pointer-events: none; + line-height: 1; +} +.sceditor-button.disabled svg { + fill: #888; +} +.sceditor-button div { + display: inline-block; + margin: 2px 0; + padding: 0; + overflow: hidden; + line-height: 0; + font-size: 0; + color: transparent; +} +.sceditor-button.has-icon div { + display: none; +} +.sceditor-button.disabled div { + opacity: 0.3; +} +.text .sceditor-button, +.text .sceditor-button div, +.sceditor-button.text, +.sceditor-button.text div, +.text-icon .sceditor-button, +.text-icon .sceditor-button div, +.sceditor-button.text-icon, +.sceditor-button.text-icon div { + display: inline-block; + width: auto; + line-height: 16px; + font-size: 1em; + color: inherit; + text-indent: 0; +} +.text-icon .sceditor-button.has-icon div, +.sceditor-button.has-icon div, +.text .sceditor-button div, +.sceditor-button.text div { + padding: 0 2px; + background: none; +} +.text .sceditor-button svg, +.sceditor-button.text svg { + display: none; +} +.text-icon .sceditor-button div, +.sceditor-button.text-icon div { + padding: 0 2px 0 20px; +} +.rtl div.sceditor-toolbar { + text-align: right; +} +.rtl .sceditor-button { + float: right; +} +.rtl div.sceditor-grip { + right: auto; + left: 0; +} +.sceditor-container { + border: 1px solid #8db2e3; +} +.sceditor-container textarea { + font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace; +} +div.sceditor-toolbar { + border-bottom: 1px solid #95a9c3; + background: #dee8f5; + background: linear-gradient(to bottom, #dee8f5 0%, #c7d8ed 29%, #ccdcee 61%, #c0d8ef 100%); +} +div.sceditor-group { + border: 1px solid #7596bf; + background: transparent; + padding: 0; + background: #cadcf0; + background: linear-gradient(to bottom, #cadcf0 24%, #bcd0e9 38%, #d0e1f7 99%); +} +.sceditor-button { + height: 16px; + padding: 3px 4px; + border-radius: 0; + background-clip: padding-box; + box-shadow: inset 0 1px #d5e3f1, inset 0 -1px #e3edfb, inset 1px 0 #cddcef, inset -1px 0 #b8ceea; +} +.sceditor-button:first-child { + border-radius: 4px 0 0 4px; + background-clip: padding-box; +} +.sceditor-button:last-child { + border-radius: 0 4px 4px 0; + background-clip: padding-box; +} +.sceditor-button div, +.sceditor-button svg { + margin: 0; +} +.sceditor-button.active { + background: #fbdbb5; + background: linear-gradient(to bottom, #fbdbb5 11%, #feb456 29%, #fdeb9f 99%); + box-shadow: inset 0 1px #ebd1b4, inset 0 -1px #ffe47f, inset -1px 0 #b8ceea; +} +.sceditor-button:hover { + background: #fef7d5; + background: linear-gradient(to bottom, #fef7d5 0%, #fae5a9 42%, #ffd048 42%, #ffe59f 100%); + box-shadow: inset 0 1px #fffbe8, inset -1px 0 #ffefc4, inset 0 -1px #fff9cc; +} +.sceditor-button:active { + background: #e7a66d; + background: linear-gradient(to bottom, #e7a66d 0%, #fcb16d 1%, #ff8d05 42%, #ffc450 100%); + box-shadow: inset 0 1px 1px #7b6645, inset 0 -1px #d19c33; +} +.sceditor-button.active:hover { + background: #dba368; + background: linear-gradient(to bottom, #dba368 0%, #ffbd79 4%, #fea335 34%, #ffc64c 66%, #fee069 100%); + box-shadow: inset 0 1px 1px #9e8255, inset 0 -1px #fcce6b; +} +.sceditor-container { + background: #a3c2ea; + background: linear-gradient(to bottom, #a3c2ea 0%, #6d92c1 39%, #577fb3 64%, #6591cc 100%); +} +.sceditor-container iframe, +.sceditor-container textarea { + border: 1px solid #646464; + background: #fff; + margin: 7px 40px; + padding: 20px; + width: calc(100% - 120px) !important; + box-shadow: 1px 1px 5px #293a52; +} diff --git a/public/assets/development/themes/square.css b/public/assets/development/themes/square.css new file mode 100644 index 0000000..a9787a8 --- /dev/null +++ b/public/assets/development/themes/square.css @@ -0,0 +1,619 @@ +/** + * Square theme + * + * This theme is best suited to short toolbars that + * don't span multiple lines. + * + * Copyright (C) 2012, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + * + * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/) + * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/) + */ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */ +/** + * Default SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2011-16, Sam Clarke + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +div.sceditor-grip, +.sceditor-button div { + background-image: url("famfamfam.png"); + background-repeat: no-repeat; + width: 16px; + height: 16px; +} +.sceditor-button-youtube div { + background-position: 0px 0px; +} +.sceditor-button-link div { + background-position: 0px -16px; +} +.sceditor-button-unlink div { + background-position: 0px -32px; +} +.sceditor-button-underline div { + background-position: 0px -48px; +} +.sceditor-button-time div { + background-position: 0px -64px; +} +.sceditor-button-table div { + background-position: 0px -80px; +} +.sceditor-button-superscript div { + background-position: 0px -96px; +} +.sceditor-button-subscript div { + background-position: 0px -112px; +} +.sceditor-button-strike div { + background-position: 0px -128px; +} +.sceditor-button-source div { + background-position: 0px -144px; +} +.sceditor-button-size div { + background-position: 0px -160px; +} +.sceditor-button-rtl div { + background-position: 0px -176px; +} +.sceditor-button-right div { + background-position: 0px -192px; +} +.sceditor-button-removeformat div { + background-position: 0px -208px; +} +.sceditor-button-quote div { + background-position: 0px -224px; +} +.sceditor-button-print div { + background-position: 0px -240px; +} +.sceditor-button-pastetext div { + background-position: 0px -256px; +} +.sceditor-button-paste div { + background-position: 0px -272px; +} +.sceditor-button-outdent div { + background-position: 0px -288px; +} +.sceditor-button-orderedlist div { + background-position: 0px -304px; +} +.sceditor-button-maximize div { + background-position: 0px -320px; +} +.sceditor-button-ltr div { + background-position: 0px -336px; +} +.sceditor-button-left div { + background-position: 0px -352px; +} +.sceditor-button-justify div { + background-position: 0px -368px; +} +.sceditor-button-italic div { + background-position: 0px -384px; +} +.sceditor-button-indent div { + background-position: 0px -400px; +} +.sceditor-button-image div { + background-position: 0px -416px; +} +.sceditor-button-horizontalrule div { + background-position: 0px -432px; +} +.sceditor-button-format div { + background-position: 0px -448px; +} +.sceditor-button-font div { + background-position: 0px -464px; +} +.sceditor-button-emoticon div { + background-position: 0px -480px; +} +.sceditor-button-email div { + background-position: 0px -496px; +} +.sceditor-button-date div { + background-position: 0px -512px; +} +.sceditor-button-cut div { + background-position: 0px -528px; +} +.sceditor-button-copy div { + background-position: 0px -544px; +} +.sceditor-button-color div { + background-position: 0px -560px; +} +.sceditor-button-code div { + background-position: 0px -576px; +} +.sceditor-button-center div { + background-position: 0px -592px; +} +.sceditor-button-bulletlist div { + background-position: 0px -608px; +} +.sceditor-button-bold div { + background-position: 0px -624px; +} +div.sceditor-grip { + background-position: 0px -640px; + width: 10px; + height: 10px; +} +.rtl div.sceditor-grip { + background-position: 0px -650px; +} +/** + * SCEditor + * http://www.sceditor.com/ + * + * Copyright (C) 2017, Sam Clarke (samclarke.com) + * + * SCEditor is licensed under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ +/*--------------------------------------------------- + LESS Elements 0.7 + --------------------------------------------------- + A set of useful LESS mixins + More info at: http://lesselements.com + ---------------------------------------------------*/ +.sceditor-container { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + position: relative; + background: #fff; + border: 1px solid #d9d9d9; + font-size: 13px; + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + color: #333; + line-height: 1; + font-weight: bold; + height: 250px; + border-radius: 4px; + background-clip: padding-box; +} +.sceditor-container *, +.sceditor-container *:before, +.sceditor-container *:after { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +.sceditor-container, +.sceditor-container div, +div.sceditor-dropdown, +div.sceditor-dropdown div { + padding: 0; + margin: 0; + z-index: 3; +} +.sceditor-container iframe, +.sceditor-container textarea { + display: block; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + line-height: 1.25; + border: 0; + outline: none; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 14px; + color: #111; + padding: 0; + margin: 5px; + resize: none; + background: #fff; + height: auto !important; + width: auto !important; + width: calc(100% - 10px) !important; + min-height: 1px; +} +.sceditor-container textarea { + margin: 7px 5px; +} +div.sceditor-dnd-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(255, 255, 255, 0.2); + border: 5px dashed #aaa; + z-index: 200; + font-size: 2em; + text-align: center; + color: #aaa; +} +div.sceditor-dnd-cover p { + position: relative; + top: 45%; + pointer-events: none; +} +div.sceditor-resize-cover { + position: absolute; + top: 0; + left: 0; + background: #000; + width: 100%; + height: 100%; + z-index: 10; + opacity: 0.3; +} +div.sceditor-grip { + overflow: hidden; + width: 10px; + height: 10px; + cursor: pointer; + position: absolute; + bottom: 0; + right: 0; + z-index: 3; + line-height: 0; +} +div.sceditor-grip.has-icon { + background-image: none; +} +.sceditor-maximize { + position: fixed; + top: 0; + left: 0; + height: 100% !important; + width: 100% !important; + border-radius: 0; + background-clip: padding-box; + z-index: 2000; +} +html.sceditor-maximize, +body.sceditor-maximize { + height: 100%; + width: 100%; + padding: 0; + margin: 0; + overflow: hidden; +} +.sceditor-maximize div.sceditor-grip { + display: none; +} +.sceditor-maximize div.sceditor-toolbar { + border-radius: 0; + background-clip: padding-box; +} +/** + * Dropdown styleing + */ +div.sceditor-dropdown { + position: absolute; + border: 1px solid #ccc; + background: #fff; + z-index: 4000; + padding: 10px; + font-weight: normal; + font-size: 15px; + border-radius: 2px; + background-clip: padding-box; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2); +} +div.sceditor-dropdown *, +div.sceditor-dropdown *:before, +div.sceditor-dropdown *:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +div.sceditor-dropdown a, +div.sceditor-dropdown a:link { + color: #333; +} +div.sceditor-dropdown form { + margin: 0; +} +div.sceditor-dropdown label { + display: block; + font-weight: bold; + color: #3c3c3c; + padding: 4px 0; +} +div.sceditor-dropdown input, +div.sceditor-dropdown textarea { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + outline: 0; + padding: 4px; + border: 1px solid #ccc; + border-top-color: #888; + margin: 0 0 .75em; + border-radius: 1px; + background-clip: padding-box; +} +div.sceditor-dropdown textarea { + padding: 6px; +} +div.sceditor-dropdown input:focus, +div.sceditor-dropdown textarea:focus { + border-color: #aaa; + border-top-color: #666; + box-shadow: inset 0 1px 5px rgba(0, 0, 0, 0.1); +} +div.sceditor-dropdown .button { + font-weight: bold; + color: #444; + padding: 6px 12px; + background: #ececec; + border: solid 1px #ccc; + border-radius: 2px; + background-clip: padding-box; + cursor: pointer; + margin: .3em 0 0; +} +div.sceditor-dropdown .button:hover { + background: #f3f3f3; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); +} +div.sceditor-font-picker, +div.sceditor-fontsize-picker, +div.sceditor-format { + padding: 6px 0; +} +div.sceditor-color-picker { + padding: 4px; +} +div.sceditor-emoticons, +div.sceditor-more-emoticons { + padding: 0; +} +.sceditor-pastetext textarea { + border: 1px solid #bbb; + width: 20em; +} +.sceditor-emoticons img, +.sceditor-more-emoticons img { + padding: 0; + cursor: pointer; + margin: 2px; +} +.sceditor-more { + border-top: 1px solid #bbb; + display: block; + text-align: center; + cursor: pointer; + font-weight: bold; + padding: 6px 0; +} +.sceditor-dropdown a:hover { + background: #eee; +} +.sceditor-fontsize-option, +.sceditor-font-option, +.sceditor-format a { + display: block; + padding: 7px 10px; + cursor: pointer; + text-decoration: none; + color: #222; +} +.sceditor-fontsize-option { + padding: 7px 13px; +} +.sceditor-color-column { + float: left; +} +.sceditor-color-option { + display: block; + border: 2px solid #fff; + height: 18px; + width: 18px; + overflow: hidden; +} +.sceditor-color-option:hover { + border: 1px solid #aaa; +} +/** + * Toolbar styleing + */ +div.sceditor-toolbar { + flex-shrink: 0; + overflow: hidden; + padding: 3px 5px 2px; + background: #f7f7f7; + border-bottom: 1px solid #c0c0c0; + line-height: 0; + text-align: left; + user-select: none; + border-radius: 3px 3px 0 0; + background-clip: padding-box; +} +div.sceditor-group { + display: inline-block; + background: #ddd; + margin: 1px 5px 1px 0; + padding: 1px; + border-bottom: 1px solid #aaa; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button { + float: left; + cursor: pointer; + padding: 3px 5px; + width: 16px; + height: 20px; + border-radius: 3px; + background-clip: padding-box; +} +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2); +} +.sceditor-button:active { + background: #fff; + box-shadow: inset 1px 1px 0 rgba(0,0,0,0.3), inset -1px 0 rgba(0,0,0,0.3), inset 0 -1px 0 rgba(0,0,0,0.2), inset 0 0 8px rgba(0,0,0,0.3); +} +.sceditor-button.disabled:hover { + background: inherit; + cursor: default; + box-shadow: none; +} +.sceditor-button, +.sceditor-button div { + display: block; +} +.sceditor-button svg { + display: inline-block; + height: 16px; + width: 16px; + margin: 2px 0; + fill: #111; + text-decoration: none; + pointer-events: none; + line-height: 1; +} +.sceditor-button.disabled svg { + fill: #888; +} +.sceditor-button div { + display: inline-block; + margin: 2px 0; + padding: 0; + overflow: hidden; + line-height: 0; + font-size: 0; + color: transparent; +} +.sceditor-button.has-icon div { + display: none; +} +.sceditor-button.disabled div { + opacity: 0.3; +} +.text .sceditor-button, +.text .sceditor-button div, +.sceditor-button.text, +.sceditor-button.text div, +.text-icon .sceditor-button, +.text-icon .sceditor-button div, +.sceditor-button.text-icon, +.sceditor-button.text-icon div { + display: inline-block; + width: auto; + line-height: 16px; + font-size: 1em; + color: inherit; + text-indent: 0; +} +.text-icon .sceditor-button.has-icon div, +.sceditor-button.has-icon div, +.text .sceditor-button div, +.sceditor-button.text div { + padding: 0 2px; + background: none; +} +.text .sceditor-button svg, +.sceditor-button.text svg { + display: none; +} +.text-icon .sceditor-button div, +.sceditor-button.text-icon div { + padding: 0 2px 0 20px; +} +.rtl div.sceditor-toolbar { + text-align: right; +} +.rtl .sceditor-button { + float: right; +} +.rtl div.sceditor-grip { + right: auto; + left: 0; +} +.sceditor-container { + border: 1px solid #d6d6d6; + border-radius: 0; + background-clip: padding-box; +} +.sceditor-container textarea { + font-family: Consolas, "Bitstream Vera Sans Mono", "Andale Mono", Monaco, "DejaVu Sans Mono", "Lucida Console", monospace; + background: #2e3436; + color: #fff; + margin: 0; + padding: 5px; +} +div.sceditor-toolbar, +div.sceditor-group { + background: #f2f2f2; + background: linear-gradient(to bottom, #f2f2f2 0%, #dddddd 89%); +} +div.sceditor-toolbar { + padding: 0; + border-bottom: 1px solid #bbb; + background-size: 100% 32px; +} +div.sceditor-group { + margin: 0; + padding: 2px 4px; + border: 0; + border-right: 1px solid #ccc; + border-left: 1px solid #eaeaea; + border-radius: 0; + background-clip: padding-box; +} +div.sceditor-group:last-child { + border-right: 0; +} +div.sceditor-group:first-child { + border-left: 0; +} +.sceditor-button { + height: 16px; + padding: 5px; + margin: 1px; + border-radius: 0; + background-clip: padding-box; +} +.sceditor-button div, +.sceditor-button svg { + margin: 0; +} +.sceditor-button.active, +.sceditor-button:hover, +.sceditor-button:active, +.sceditor-button.active:hover { + margin: 0; + box-shadow: none; +} +.sceditor-button.active { + background: #f4f4f4; + border: 1px solid #ccc; +} +.sceditor-button:hover { + background: #fefefe; + border: 1px solid #ddd; +} +.sceditor-button.disabled:hover { + margin: 1px; + border: 0; +} +.sceditor-button:active { + background: #eee; + border: 1px solid #ccc; +} +.sceditor-button.active:hover { + background: #f8f8f8; + border: 1px solid #ddd; +} diff --git a/public/assets/fonts/ionicons.eot b/public/assets/fonts/ionicons.eot deleted file mode 100644 index 92a3f20a39267ae7f45144f412a995a663730360..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120724 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

    @FHUiu&=5w(f-3S1h*&)&h2 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 - - - - -Created by FontForge 20120731 at Thu Dec 4 09:51:48 2014 - By Adam Bradley -Created by Adam Bradley with FontForge 2.0 (http://fontforge.sf.net) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/assets/fonts/ionicons.ttf b/public/assets/fonts/ionicons.ttf deleted file mode 100644 index c4e4632486d863337c1c73478ddb3c20726c55a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188508 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

    8E#xmG!w{$tO5=<$!EHPzZXo}IBc1_sqA&q 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 diff --git a/public/assets/fonts/ionicons.woff b/public/assets/fonts/ionicons.woff deleted file mode 100644 index 5f3a14e0a5ca6d20cc4fac708979e807b0d51bc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67904 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

  • F(S(gioucm()OHA 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 E0LEz`i~s-t diff --git a/public/assets/fonts/wysibbiconfont-wb.eot b/public/assets/fonts/wysibbiconfont-wb.eot new file mode 100644 index 0000000000000000000000000000000000000000..170c2d360a660bb7f3ab1fde9e7abb275ca712b3 GIT binary patch literal 4057 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 Z*g25(;)m#snJkstnGJ_dzlZ;m^xt-?UpxQ+ literal 0 HcmV?d00001 diff --git a/public/assets/fonts/wysibbiconfont-wb.woff b/public/assets/fonts/wysibbiconfont-wb.woff new file mode 100644 index 0000000000000000000000000000000000000000..cf2cf31cf11d5b664c47f217bc7b7877c3ac4905 GIT binary patch literal 4732 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 literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/angel.png b/public/assets/img/emoticons/angel.png new file mode 100644 index 0000000000000000000000000000000000000000..4792225c2749989ddb78a3804dcd5bb9617fae0e GIT binary patch literal 1182 zcmeAS@N?(olHy`uVBq!ia0vp^B0wy`!3-qNPStf`U|?zr@Ck7Zci{N{|38q#L~h#5 zKtTuzk%fx@8JKKbCcyPVbU`#CD|XifSp*@0ve8Z;#{kJ_7fuKp(*(Ft&L#ptcD5I1 zjt^IEAa|Z0SH2Hteh5!t5O;An@057%=~;X;3kB!Zi!ILJS(3rKB#U=xKHt(p{^ezY ztDD8v)C#TZm0aHUlC42`uMGo|eooEm^)h={uxx(od!E@arS60bg+oW`N zvhdxhqIcKJ-Q8+*uTSXScBO|Cg`Z3nemYI$=?sylizJ@U5q&;S?8QpyS4$*bEtPn+ zO!C!o$=53+-|SX;yH@J$dg-?tW#4U(ez#Hj-DcT$o8{hblzzWi=KU6#_uCcT?~?zx zL-ylNxsSW#KJHWccvSh*N!8DL*raOpXXJ6URM8k zP3!k%)!&!ZeqUApeNE%{4b9)Tw13~y`F&ga&sEhw*VX^r(D-vp>kp87SLg3d^}n|? z{@&I6cU$A%9U#*DcUR-zJHmGr|M#{2KL8@_ z{|~kQKLR41|BrP4KXUp1Sm*y^-TzPY|3B6H|5X3~Gq?ZG-TuD-BKQ9<-GON@B&WO| zm?#}eg8YIR7#KOZxMkGT^$pC-EUc`pZS3q5D{JeTnp@jDI=g1go;QE>rtN!=pE!By z?D_K-uRMMJ^5x4{?>~I{{N>xv-+%wT{=fYw(2Q)}xpMZ*6;&6%*lol**W7t(;Kyqt7T-)&AA3A*nqln4tI|ZM%G#~C1 zJ}xWMw{CXi>?{@$llKujr8SD)+}Nn>UZy)gXeN`;72oJRUo`?)IQViRuLRFpz$!9@ zd;Zp>;}4bH`DCp&*jM@T&b3K8=5`YvPj$MTrc+c9 zvAg8qmT8-0>RrPom|OZRmfofAKih2O>U|pOr)J2CDcq|RHN7f&^rvLp28P6hgvq6R zJ*Ut1eJ!4p%3P$_#B;`G(kwBtS(9we@CZ2SFfe>rJ8`GnuXEbP0l+XkK D{&Ew< literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/angry.png b/public/assets/img/emoticons/angry.png new file mode 100644 index 0000000000000000000000000000000000000000..7bec8e4bab65d39fa8be3bcd58ba833d3f2154be GIT binary patch literal 781 zcmV+o1M>WdP)+}4Jch)?7lhN}T&oKa2 zHTt>X-011Wuuf(@&njhkD4M2ad|k}+adUzBtk)jAHXdu<%0bHp34&EB1j?0Y`n?c; zypW0euZjcThPIstJeFl*HGTa|CISr_jCbfTVW!x$7Nad1gsYSo+$q6l52t`{OJiv4 zOi*#lg)w~7_{-&(=+KAf0V(&J~8O^9hPFeO-&n44|c+46gpIbp|yE1e~>lC+$VZ~lBL6}}yX2$xFel=zW|ui0p|4B^8i@NFx=z^^%Q zf1HjV@1mponj2z}r(~~D|GwAK;n|pv!0sYK7(j&%7Sp_EeIELM%qDF6V~Jp6`smf0 z@8j^vJ&ACCpN*lvaxu(+;dOa5-TzZIdcMqn!UQ3J)4Xl?uy5~7so98`I_Sc`vR++<)>(xmd*cxUCsUmJ*Iy3VWQbJ00000 LNkvXXu0mjfU{`#- literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/blink.png b/public/assets/img/emoticons/blink.png new file mode 100644 index 0000000000000000000000000000000000000000..ff529f1216550e49223946ee805750560cee6141 GIT binary patch literal 972 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqMyzV3G;&32_Z~;Q0UlKaivaigp4S z3nZgmI3a9^EzTwa*f&-02u zFRT2#ruO@?;_u5!zppC)zNYf~hU)K|>c4LRk;d;^n!j&r{JE<1=c@9bTk3y+?7Kju z`S+&E-&;VW`uDE--v^q1A8P))t@7^<5UKsUtMc!G`o9O7{~l@md#v^EiPryns{ik) z{lBmJ|GwJ)2SB9$|DpQ-$6Eh^Qctx1Kh^sGR2!Ho?l}Z{1Cxt-NswPK0|O%`7q^U> zx`CO6m332dYkNm$*X()okDoYs>g@R|&tJZL`Re_LPmDJ|X#ka4c)B=-R4^td9AT1> zkZfp}+3oM->e|%bEqSDwV-x4=TNTr$RzzfSZgymyY4NnjTis{=wfpx3l2wkMWD!dV zFPdvmesdYG_wz4i&#g{wZf1!*`bJ$ZFg*PJj>5Z-?)~EN4isCU&|i02=Ihzn=BwAU zpV}hYo>|Py+WO{KLy`l-w3R*^1Q@tZ{qPNK?^wy)xl5$nbHM_ye3e}!|{`DF^ zep6ueJc{CJX3va(YRZv!I}zm;$C^aTv*!n_3+4l>V-_wx1al9xLX a!N9R`TtBz_)LH-f@2?$_T|NN$ zkNo$~gee5+1efOYiRR-OD~_wFaNZEyOoiiGO6(V{*p8ccwN=aXc45eWzs02ZJ)yB? zA;0G*=BaR;x8f2a_wCehFO48A?z?GlN%2JsNym)5-=bmP&}eEF@O>&{y@dgSIqQcj za2&VbzK|NOrlDY_P$b+*{o~$!RY*@;u=sDTVZnZJztxCqnI8Kj6(4)W@uG1QC#!s!!xNsu5o;TUW9uw>Ianilse?rZ zSZ_r3i2dSvKwS44ac>m8o3F(-GJ&_9Vt86NoJHjVrEsXHFlYNx0laOiAgOO6j`12? z3v^;=df4?<<`z=dP9a;PC23|N?*}P)(K?FzH9~1y#1Fakyf^mTRSiOU-&28YbOQD{ zY82L__8%56+tB?!yvUfC(n}xARmJI&hJmy!?)T^A r>rV>oK*4drhi@F0@BXU5{;&KBuZV}c&5;fV00000NkvXXu0mjf6a%Ls literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/cheerful.png b/public/assets/img/emoticons/cheerful.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c5cb8499f07ff2a47573f5cb8f6b054c96688a GIT binary patch literal 753 zcmVRvCV6*Z3k;x@!E*iw(Vz+ZQHhO>*9M=^Ex*@W|GscQ|RvhEC74T(&nM- z?N(w>TuOXM6d3;T{XTLITJz)^>#mo0QA@mzx)J94$U97@XffCingK^>`FT8k< zBlsC2Xd|HwaXwM~?|a^us#L?+sDr76(bBON3$<#@7ArBFB*7oAx`Ce+Vrn(81EJ2YL)Zw*i(eJ5T^5e{PEm9IbKgt;m;S0TvsxandyI@Sva z=bco2E45#x#8jpP{m16Qz+>AdR#WJx##}c4ZD^y6w9a@;`RhTW1VKgcbrC)ilFkdVj zo1~j2s*qX|Bvt_-5_V_+s?bzN!h9(S1JLq|CkqF}!tse58H|*b+Yw7xV5OZ=!_umP zu||QJYzaof{L%80$0}i2OIQ#O8s-)uaD~K5E4{czB^JtLm`V-C;1ORGeeA}V z%oR#`R$F55un#(ZaL3EH+q3Y>l^^zhC*N_$L}DOj^CX+fhK~B6=T{FLe8!Pw|FT0Y zWHj^}4W@tLfiZe>Qz=2#V8~=(td$+~2FnuOa0bN;@UOSw;_%yZ&gj|igOO8yt19dJ z+Y_zdy5Z-i95-GZRxV#J&|D6^?2N+C+|cv82fBZ8NB;XRI6y5vf2;k*%hv`A>_l$5M}W!&F5WSCa}6$WKFH$x?b`1Eg~ByNbD@(JlV7QO7_|&`MZ;a?oJiHyI$sApTNEC@((8pKA9@`beho98A4ANi9Meq{Cu9sisN$!Sil6sLf8Hzed7td(L-L@3g6GGe7~&r^St8E%PK#wsr|mJ`1`Wb@2kqc zuc`dLq5Au#`tMsBzi(;&zOC`+s?wjU%71RD{{gb^0+Ht5n<{^A0g>w8yXt=*X#Rbu z`R}&MzdJyr_V2FBzk90x9;p9&p!x5S*1yME|DI_5zo+{Dp4$KWs{ik+{eJ*N>i-|A z|9_LYcR#bQuaDoJ_jOO-D--WPu>}hKb$hKi)-XU6gsFF!QM=;zH1y?Ei$ZRRa%%323}dPJZ2@};&<`E%*hr%QXD zY+$H%EsJQ7U_GjK oscargruno@mac.com +Andy Fedosjeenko, aka Nightwolf -> bobo@animevanguard.com + +Copyright (C) 2001-Infinity, Oscar Gruno & Andy Fedosjeenko + +You can redistribute these files as much as you like, as long as you keep this file with them and give us the proper credit. You may even rape them if you please, just give us credit for our work. \ No newline at end of file diff --git a/public/assets/img/emoticons/cwy.png b/public/assets/img/emoticons/cwy.png new file mode 100644 index 0000000000000000000000000000000000000000..58ee08f80b3b5d8b0528bb2ddcf784b9f3e54e9d GIT binary patch literal 877 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqMyzV7wCG6XF`~!14e8e;`Q<6zv2u z7Dz_Ba6;G+TkKDKaW)ai_TtR(;mQr<&hz8S_uET4-CsT!=P7`@LL*(fq ziRW`fpU)F}u~Pcg5{Xw!C0;F)e6?Ki&2FW)Yo*?Kt z>Gzvu-fxk4zg^+|F8Pl;WIyhd`?yQ)<36R2N0mRFRQ}wCsn?kQTujA{rf4E@8{LOU)K70UghUy z_1~A(eqUApeNE%{4b9)Tw13~${&P#~&u#6$H`V{%()fE<^WSZae|LaL^WOvQ|MxWi z-v=VC{||sj`~O32U~KOEE}svKX7!RFzhGcIb8>OZsHq#6Sy)*&HMh2Rbau_2H~;vF zlc&y}zw-R$%a^a-fB3|B^OFWpsiLQgV@L&K?uF*6&5ixB&8sWZ;vMa+dN}X6H(ix92~X myjSin)tm13$L^E9Eo0W*fa-mcM_&TNm%-E3&t;ucLK6Twvy<8Y literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/devil.png b/public/assets/img/emoticons/devil.png new file mode 100644 index 0000000000000000000000000000000000000000..7d8226a277a6df575c5396daa51a4e24f348befd GIT binary patch literal 1012 zcmYjP4NOy46h800()WfIhBo|(mR6<1F@_s6)G1UbQQXAJPZ>65Zo+g*F&k_+nA9I| zDk!4qSY#^>Qzl_ZK!-zw8DaoMtZUhzB2a;q6q&8%&rs>zZOx*|cfWh`o$sD=bMHAJ zjo-*|_jd;XPTVF*0^S$l<-<^zR}WbJ1|WIT50hdMLMa|hir57lyc)j{F~2JE|2Mm` zvS21T(xn8rSmfmRa5mB`%Tlr^XDs6~G9PBEh@B#2 z_BSf}+YK4b zYx=)Fr;ryt%sr2;ne0X@6`Uu`lO}5}w2NaE98!~=TkHpYvG?_=CHAYCO098pwC~XM z@N`{$Q(essW!}Y$RkgR5ra45C=?wM2pt@pWnky+s6pR#neb?XjGC5}=-d-s#!kJc5Gs0B7R!PymVCC4ZK z{n@y!sf4>~TZDX^Y!r2Es(zHz$nl+%{aTs(?v9CF%+B*Y;tP8d%lD9g-VzjjCe3UJ z(+x*;`ZlGTN{Sawo6>*xJA78H;VBNBG3~CVw;y5W9u=1zl%8p6P{EtQ**ktqEj@() O2I6AlC6}Xj9{CSQAEAZ- literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/dizzy.png b/public/assets/img/emoticons/dizzy.png new file mode 100644 index 0000000000000000000000000000000000000000..82184642f9284288a0eb154fa17cbb0a37f53f29 GIT binary patch literal 991 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqMyzVA2Ti32_Z~;Q0UlKaivaigp4S z3nZgmI3a9^EzTwa*f&$9|Y z&ny1Ctn%}k+V9IszppC)zNYf~y6W#6s=sfl|Gou88ozI8{=Tj8=c>}5tIB__tNyv6 z`sbGVAE3xxAkzGMOXcrf^}i1^|31|GcU$G(9UxNscUR@#J=K2?)c-xw`uAA#-(#(R zPqhBuQ~iHW?f-q%|M%7YKL8^2{}0vwKhpUBNb~<=AkzB(MEn0!t^ZH8fhi>F`U@3c z(oru7@(X5QVC3ZDmQhnTFtf0-Zfb6A@96BBJ#YT;6DLodJ%8o-%a<=-z5nou@#ZHD zpi+BJ7srqa#^i(}OcD~34GlB9{heH0oBF#Yk2G^^;(UFpV%pS-h)mASj;u2+p7waF zpPOa5aIxYskG{#AIw#gWu`0aRl^Z?n{M+nvrIXUxIX89P^p9G#Zrwi7j^D40bzCDG znrwdQ7GK?-f1mHz+oOrM?l?Fq?*5nh%3+^X$BUId8w41*PW?E0gGYQ$*z`wwoFXA7 z=kF^N4#;2@SU)>z_M5c~A|YpA$8JhI-1hbL_4U!acdS;r$E~9A`m$ZRtVhb*TcMk8 zcP+ZJq_Ih9Tj^cy>nm2TkK9{SmtI=6Vx~gCgmC@ME4^zkdVOVm^{V&g#D7*c3dg&n zpI_Nh>Nj)Ey{M_8vKd;(xIU@1Z%U3ztPBmUbj+$)(5z&wf&$9|Y z&ny1Ctn%}k+V9JXzb`BOzN-BDn#%7Rs=sfl|Gou88ozI8{=Tj8=c>}5>#Bclss918 z?*ftL-wqERmi@(X5Q zVC3ZDmQhnTFtf0-Zfb6A@96BBJ#YT;6DLodJ%8o-%a<=-z5nou@#ZHDpi)~;7srqa z#^i(}OcD~34GlB9{heH0oBF#Yk2G^^;(UFpV%pS-h)mASj;u2+p7waFpPON~aIxYs zkG{#AIw#gWu`0axNq6?Nvv0G{l}<`$=iJnF(?4p}x_zmi(@MYY`^DoOD7HYMzwUI* z*R|1C&6_&DJu=A6Td-h4?EYO=0SrzdY7ra^tf_yNDRN$0y2G1^HT2W+Jch#S%^jLs zPu}BlaJ;(bp6ctXSFf*)UcPSMj@?T4xK%V>U$#qsyQ?(&`kKhiMPbo_Q)Zh41RUA6 zF8$#v)9kSIQHJ|hTiIUm5zsiXdiu>Pw$&HAM7h(-%+lO{z5dW3qn|f7n0ZT8QF?Z0 zSjmEw651zb%be=VY<(K~>M85WhmCw)Z#FO(^7MIo_4IOlG-Y7puz6u}Hnxdrj<3l9 nM|S>{I!#kk&AJMOWJU({^u#v7H8Y!mVb9>{>gTe~DWM4f#N@^6 literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/face.png b/public/assets/img/emoticons/face.png new file mode 100644 index 0000000000000000000000000000000000000000..04ad4b7a2aaf344fb64c6d11d5d4269311cc4128 GIT binary patch literal 793 zcmV+!1LpjRP)u_2(=mzY|^9m zcNzY8p&0jHl>xC1ue%C(B2`cIe*H`_g54&>hN}^`aP-}b`F;~3t$NJtR^hXU^B~sc z8J=AV>TbC(ZL;|n1rH}2HAuQ^0Z$#^u0zsQi-euvArqnExR_lG zW>8;me77925i^p`T1E(?YL7=~(q1D_+EX38+ZFib3EtB$sB|fK`lB~wA=+a?f(GE0 zAmTe$!@*gDgq04zM~~nk6()YGzyp`33-nYe`09qY%lJ@}u9~HY>%XeGq}`0zfDz#a z4Q4l1;irdl1o)B^Y_o1xAx?)2*YXIQk{o8l2MvfcX)w20hW0lK1=uJBy@4hz;=?8y zSO($D3~_b>2lR+EstK%u<)b12PD(+a|ELVCBRpt;295JodNGIIik%PaX7peNsECIhDHrY z4Cx8eBHE;af2RynKbOJrUI8AwG);h~FTSzA`SYoK_;<+=X;6tn;ysD?X*mJSw`mYL zs)T<>6{deJgXiNy{CF4d-Pgj9f+9UVMpFk~D};A*C4zh92p?7wz|nUFf_r4}Zmhu6 zkEM+5d@LI@Gs%zMYA=UR?#YJd`%=vQRgO6V<~CID_o<&sG5%!{tZ(Pz!#groe>p6E zecg9yI(~XE8_loe!}Dn&+#eO7@!4Gba91Yoy*O?4*Vig9*cYfKD!}n~{%`dc>{|9W X#NehlfXChQ00000NkvXXu0mjftdMl% literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/getlost.png b/public/assets/img/emoticons/getlost.png new file mode 100644 index 0000000000000000000000000000000000000000..ac87dcea2511cb5276398989cb23c5f434c3e462 GIT binary patch literal 792 zcmV+z1LypSP)eTtlbqs(* zg?rp`VQesQ#OcJ#L0o7iEG{S~qEJk{_2`X>IQm`=#*1Ze^lD)5Qe*UQ3I2H{ z6AxdP09J<(M-v_)P9#RZe<2I@VJ$q<26*QfdivJmU|b7VuNu2GGJN%TDp*}U=SVoA z_Kr*Awz~h2a3bH50sd7Zux0{QP4KT6;afDoGo^!jKn-iX42|z);f{-AH&|}4FoF2K z=)+uiX7unc8<~X3c$k16P*<`z0duzmv{{okF46MnNz25X%7x zeFw2bamJz!-U$+R%1M}yxz93~a7!q(SlblvP^*5b94rxt2rc~c8hFN3aJI^@SCEIf zPtrq#19JspZlZm2d}5F!z&tJ7v>FEk3fSu<*#092J+G!RVYg8DcEPKe+?;sE)$q?! z;(|c3z|fG4Mlhp>XG95CyA0;iJZyZQiC-T`WWqOu!gC(KJ^|)xG2Gp9HXP~&CC{lz z!i#39_ft%!fV)cuYn2Gwzh`6Vqcl8vWgHWp9lWuB`1iRqSgS>Fb;ttS!#k!Tl$_|G zPYzd`6xK=+cK*!9+UM!`^}Ylq`<^+3LWYix(RSlo=`hpE+3Un`wnzeE_HC~f!CaJ! z?O(GvS=CcXpqwps^rBu4Up$b6wV$)G_g^mdi-^Kp`p&`jZ`s)RHUsnTr{dFl5~5!Y z!PnPAG%vqBnuP8*QnB`BI#xeRL+6Xh_=Se};N@{quCM49>?=eUVdLrI%c5Vfhspnm Wr;i>N%`IgB0000w3l4w}@=$6W=&NVrL2Ofli?Vy`m=?_)fJ8oLVk> zx<%kzx6qYUve!1r-<>RUcdGE+^)mPR1nzB@e>hR_$yC9o(}bSR5PG^u?D-tw=kr8f ztdxAUMD*2Cu~*B)UM&}Yy+Z8GZuz%s#ow-#c)MQm?MCT$8zkOslz6vU>fL7P_nW2O zZ5u#5KOR;5bW-v2UYXB_kU4b|T_)qmdtB8}g-G=JaL_;W+`&n@*oK*n7l(){~C z^Y25=e-G6EJ<|I3So7awt$$Cn{y$Lv{}6~Y{yzdD&Hs-z{yzqiTK}JD{s+oF(f;F@2U~D=#C$9oVvwBI8UobG9Ik~uH)YJ{kEUc`Xnp@jDI=g1in}7Vo$x~;~UwQuW z<;z#^KYU`m`AGw)RNT|WF{FYq_X2Y{6N8Au!<^ge%x`bInsa;G**E{*#GT!7{QgH( zf&ZNjDhVlK{)rnkDs+q(tDR?AY+X`VyLF+4r%$!JQ1JA5V3TdM#!`i%OW)r!sB$p!%dGjLwpq|Mb-Dk+ z1cL{kf9~OTmwwF3a9O=H))Y|)8jJaXi1i50`LS-c(BjwW{;st%OM z?pnC*DZ`@q2NUK>1qSWlOoJFh$P)<vrd=TL4e zq4DMmQWl$jRfs6r>rbt8DnKY((vC>TB#QH-3t8tIIr|`B2_9p?UC!#;qNx`DunF(6S5i5 zD*xr*w$K=DzgrJ?l?n^Lmf-%&Qib#ssqmF8Zxzv2O|&)3AtH{sjsFv7jsyJ$cE;aUEGi!>+Kr2%M@7pxd_cK@J%;Kg|BUXA)n7A(5FXu zgc8Rkl5v57WVC}}Jp$cY`07-!?mJp{vqh-zSH^NiDX+HyIw) zvxG$>wE96I)4<=Tf^)A7OFtE2^4%Ofa7l^~o)-DT{`!w6bKu-3gRfr2TOuhF?A5Yp zy;TigtrE^XGA#d6h}n;F@xz_`uD=qF$fswbXS7uDYA)<_bMAw3c&imGAUJj(kiovS z7)w7Cux#(68K9g+etL literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/heart.png b/public/assets/img/emoticons/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..451058d6d02646eb1b0fac70cde05c02c9864d28 GIT binary patch literal 572 zcmV-C0>k}@P)xZEyy?R`y=9^UeZv$sUe$-T>!cM1 zkZs(pGk#hUyl8@t?1I~_6oe^S&+{gww`K}`Pun2e|5ghJq1?$-bU1ip+5b#-9XiM417EWeG zqHAlLugifCw6Ze^;286VCy0w(P~asa`g?;X3WA@if~=>o=$g+;gLevmTf)I^9#*Wk zxC`uqMlLBQ_t!)!QpR{(6ug-R&JVOgm!+6w7dQ@nD^NB}2>`F8fakTv@qSiptpo@R z$3Z(DvUIvaGkmSsO7jf(yP)CM&JFdYf!)gfJZNM#fZqxJS6={J%R+Dw5WwsJ0000< KMNUMnLSTaAWev6f literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/kissing.png b/public/assets/img/emoticons/kissing.png new file mode 100644 index 0000000000000000000000000000000000000000..28d775242312a519611bcf2b29752c4765abb2cb GIT binary patch literal 793 zcmV+!1LpjRP)0YJwXLyT+YM@4acxAkZQJg(eec?~?H$i#GkE*`U-j=O+9c2Md5+I90M-=_ zu0JO^k`&@3;yI$0urr+IvXi(?M89~#a^lqou1UwnH%rj7RRK?v4(@s_x_*=3_vZ_7 z-<4?)+Hl(h!VQFt==$pELby8&2n?7I9A+qO#A=TLz9ub}_9^h`Lpc!I{?lheETQJ+ z3sUAAewK0P(6|}lDGM-d1*WVBPg)QfBXPe0{#GrV2NgK{W+84qFL_qz8!1d9zS{Cm zDFTB=geNR4AsJ!$ACiX0%q;aaX<*+a!_SZNOg|@5#lq7bxjqB_P6N%tOfnYW{1dV{ z4aUrQOqdZGq2+gK;XbOs+^=Q0|FTp;dWu;1%I3F9c&Xt@77{FE5hfYVW70&kG$PQe zhqqRRiCH@>D zzpr~QNyU#3WTN5aTugtGkEsvyQ1?tWzP~da_gs{c`1?xm!9Gva6E23+T$b>IU041B XKE9Q6@a!0~00000NkvXXu0mjf6^?Pr literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/laughing.png b/public/assets/img/emoticons/laughing.png new file mode 100644 index 0000000000000000000000000000000000000000..d65f35ec82967bee26777dbc0deb39f2af387807 GIT binary patch literal 912 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 T^SL{P00000NkvXXu0mjf0kd`( literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/shocked.png b/public/assets/img/emoticons/shocked.png new file mode 100644 index 0000000000000000000000000000000000000000..f3dfb11f8d22ddb7d678befd190322f0df7d330b GIT binary patch literal 780 zcmV+n1M~ceP)%Z-~r5!eHV=V(9x9l3*TC!ZD$NbDBd<*J|vJC}Hc8W2;hxuO5#D_w8OxurHzV zj!VPVJO0RGqDxMVWnftcEa~80)E+#u_sU_c5uxtAB;0Xv=qeZMDU2Y#&;KwLj!88y zA_+STyI;`2HABN6 zl*8O0!rEVHc;xDEPI{(L_-5YwDKu0S4UL3C!4lms^050wHD#%SV^jfas|4Hm8Tjq- zXioT=P}n_FDuR=Si$zW*6lj@JNMN@HYbh|Iowke zILicV-h{X(#`H$l8=#^od@NCbG{lmY{#lcvS0b8qx4T&;g5*!o6U>mwQ8^*E> zZ2p;y<wT=QzvmpDrKh4Y+NgUg9(r0ib9E-Hjagn9>hn{v@oN&3l|K~)idpAM zFJN={;(;hE|D25N|5CA&PvoW2bqY3qOUCNA37C057N6b|;n^Glx37n2UVeKt3LS66 zV)@H>EPWP-wil!E3k~tX%fkYY&p&wKVeTF3jdDiX^EVMA<@NM@BxVUd&vS9TUq&SzICW!9`=)~;pNuV*u8 zW;SSJG-+is>tMB>%w#i_(QXpE(^L+pSEGI`JE@?OsDyO7IwC9~fmuAsH7LF-t8dxavmvPErYi{8c-vz;ww2V2}O_V^uK zi92|b4{)R(RpW!Ju!&PvWyW|3Q=|!H3t9+H$ zc&o1SR^Q;QxxrU^o3HLRU;Q1vhI{(y#B92i3khQeSdTV~X&i04;&7C&f;rFqut`+9$Q(+~4G{fByb_@!x!VUWZn4 z&MwypDi>GA|L>9vt-Z3mk8_qd%RIGN<=lmPd!p{M9ptLC)Ct`G=j~Mn-BvT@C#s%t zGw(fq7jArEt6Kqsg4v_5OI|U!WP)sma(ylZQFMKv2EM7(e57hms>Y_qh8J8yjQxXT!#TTQ22BG zIsQ8dAWkBlBWei?!)Y!%iQDXm+fSHGy!ya3!8r700(vr3a5fp>sMn+GHyM6^J{I>~ z83fUW+xie5P8>sYef4xK933WX3><;YVTRIXto4}SZqj3^P=!w)ihyX#F&w*FsJr=s zfcb`>6&$=Zek8n8M*-7E15-!CJ9!kg#z@?6f~QpvTZsx~Z^q*0^ZaK;>`q}2@m2ae ziP#u4!#i;lOGri>#V{m2|AcS>jZ_Cqt^z+l&LjPt2$Tv>d*u2McsflqiX%xTk%%;N zB$$aKS-94zhoeG;xnGlT|7C$fdWux|O4?fqJXP-`2kZiqM-oRcBEQjVgsWDA<@6N% z_)wS-z9RmKawHEg6H9*IOEVZ_M7wN?!rB?>J3oPfp`c&8hr!q-w?h~YKa=rO=MMAA`- zWYowAnRn2@LU)Z4maJsVeie%!?hX;cm!-nfAG#_CmI66E4H}-#)&Plz4J={NkO5o$ zdX@=n`7$j0lz{Pfqwv5b0YZ4%&V&8+A5TWYRv?4BR>f1==+t4eTPvWn7HiEKxT}@0 z<)vWpmjq0I6pbJ5Zrx$*V$Ltp|7; literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/silly.png b/public/assets/img/emoticons/silly.png new file mode 100644 index 0000000000000000000000000000000000000000..d4497d9aee01396181aea5545c6fe0dca0109df9 GIT binary patch literal 930 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqMyzVEi566XF`~!14e8e;`Q<6zv2u z7Dz_Ba6;G+TbxYw3l4w}@=$6W=&NVrL2Ofli?Vy`m=?_)fJ8oLVk> zx<%kzx6qYUve!1r-<>RUcdGE+^)mPR1nzB@e>hR_$yC9o(}bSR5PG^u?D-tw=kr8f ztdxAUMD*2Cu~*B)UM&}Yy+Z8GZuz%s#ow-#c)MQm?MCT$8zkOslz6vU>fL7P_ZubN zZRu6+iEh{=8S_^FGRQY^K?aLX}uZLy7 z9+CfgT=Cmcxo^kiznxI{c2e=%8KrM$RKA~5_kYYQTz8m z{of<4e~&f)J<axjH`bc<@hWMQr@qlfRFtst7oT{fXaQUAxoP`o;g{3duU9-ViwP z^JoiGH_s64NKH?|Qx-DngyP!LpG#4!_pVJlrkv&E)?A#z(C`H(RTJcxRbY zd(4MXPQdljG(WxEocw$pzYuCw-AHKZ*8hBkwztDnm{r-UW|D6p}* literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/sleeping.png b/public/assets/img/emoticons/sleeping.png new file mode 100644 index 0000000000000000000000000000000000000000..2b97a8033ee0907a0600015f7d1c576b6cafb9f3 GIT binary patch literal 1039 zcmeAS@N?(olHy`uVBq!ia0vp^B0wy~!3-oDVtf1<7?|_|d_r8q9XS5~{|_W-fufy2 z#sbM`7fuKpVvDnhK(-fWjt^IEAa|Z0SH2Hteh5!t5O;An@057%=~;X;3kB!Zi!ILJ zS(3rKB#U=xKHt(p{^ezYtDD8v)C#TZm0aHUlC42`uMGo|eooEm^)h={u zxx(od!E@arS60bg+oW`NvhdxhqIcKJ-Rl#&w_WMsMByh>g`Z9nc{)Sn=^}~eb3~ud z6ML~z`qdJNS4$;cEt7n;T=Mk_$v3-|-maB;yI%V3M%i~8q~C3nez#fn-DbJ>n`Pc_ zk$Jye;r%Z8k2~c)?vnesPwC@PvTidzMWC~c1HdCNu}?nRKA~A|9)BP=UJ7X=T&}Q zR{wcT>-S~V-XUseBoP2=}<&EGdPf8W&leM|fIEuG)Dwf|gI{c~OY&kc<~x3vBM zxp#H`-c|MxWi-v=VC{||sj`~O4j|Brx3=l>(!|BrS4KL(O||DWjo2daFc|Np7p z|EKyJJ56r^6OejIkY6wZ10yFFw~U&)ftiJsbyIU|dq-#2?0NH#pE!By?D;FtU%q_# z>ivgLj5j}N0F`EXx;TbZFy>xho-Q3A(DpDPSNYr8+}mDjuWUPPv@LLV@7en8#mRe* zpFeZaT|r2&FG60SWvyi3%e{<&m(3LzA98dA@G7sX$aIVmp7gsdtzys2g?2@}yN`+I z-}?XOeDllHYjXO_8mxIvYt>zik9w$*$UFZypk`~Lmg2j3jKVY&6{ zH}mil&p*Gke|CNQHUBaOKApcGRA--0-#pXj(d1tzU$*{jNRHO|A7IuTq%qUQ?c>u@ z<%TWF??Tubj|g^IiC+JHj_H%!4k3k)6*^MA7EiA8ZDRQO-FJVt5N~_^%lKHUhKY3^ ze+`+KSZh`tfBrpyQQb_N$KL5-fq}%2Ws~pOY)um>C^-4Hyz2gWhX=dEe}6l-b8$Bt zgY|OrdsA1IlhBw>zi+Dlz6C@Yzi(;&zOC`+y6T@Bs()^&{{gb^0+Ht5n<{^Asrr%D;Q6{~oCSd!YI6k=DP*n*Sba{d=PI|DNjqdusphtNy>Q z_WuD8ssDcnL>m7e0g>ka#~S}114*s_Pc;7n6+hAb|5WS$Q|t;0FJHcT|KStk%}*LYrM8|fjv*C{ zsS`{WU2_m|+dsSK&&LPvEy;hhSz;wA>BX5k#0e<=<8)2dtA*dM*xxpBj_O?P$vm-NJi9X9PTp5}XZ z(c4>b)3U!_zJAQ;c3$?=?`zApHu%?VaF6|8cKGq4h+l>ITW7`Rbsjl)IegN=e2aJM zL?2^L3ucVkf3az_4fVboFyt=akR{0C-T%{{R30 literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/tongue.png b/public/assets/img/emoticons/tongue.png new file mode 100644 index 0000000000000000000000000000000000000000..73da3e34ba3840435b21ef55c80188ea3841f60c GIT binary patch literal 981 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqMyzV3H5;32_Z~;Q0UlKaivaigp4S z3nZgmI3a9^EzTwa*yCeLJJ_{iOW&QwrbDt9-w#_Vc{r&&w)5 zuc`gMtn~Y;^6zUZzptzQzM=a2ruy$&K&0{emgeu<8h@@T|GBRE=Z5N^Tk3y++`B-e z`S+IU-@EF6A87u4sQK@X+P}Lh|L&>&d!YXBk=DP*n*Sba{d=PIpNZjrZu44EyIT4g8&ya+rN5k9l3~ z61ORL-psu5sp8<^h1Tuem0gj;7;_=; z=h?bIH&+#dGv(gWx2&ud?0Yi#Z{Ngu`cK~Mo?rj}&yPRo{+)c?|er4V(VBj-&y85}Sb4q9e03`9*5&!@I literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/unsure.png b/public/assets/img/emoticons/unsure.png new file mode 100644 index 0000000000000000000000000000000000000000..87c7599719d6be5d1766b657921f69e45cedf2aa GIT binary patch literal 763 zcmVwym<)wl`SYiq}T8w#~6UZQHhOt6Y^%;h+1?=%qA#$@g+~zUzAdfb$Cd zp1du#-1|{hWxtED204Di|A;=s6(3!7t2tj+E7~`LO;f3jGir@#=+R7foEHe1k7`>uEJo(mdxLhU>m*= z&Z$c%_}J%~CaI-G0d2krdq1R+Ze#@Ug*UwSWGJlN8aO6sWZ-|E^6TA77^@^$*q@9y z9|+>4*YSnlW&DyzQ*}-s6M_N>PkCFv8s>T#Rx?wv`<-x3_%&bHI+ibiolO^~DYOr$ zU}=V(U}EIl`plJjuA4;AGBW6>QxKSn9;k=A>YL zOB{B+8p;X3gv$N}w+kU};YxCV!8{ zTlWQW!W&!<_SQomM?qgCfTdnSi_eY3-YX~go1!mB#qz#H%>EODT`$w002ovPDHLkV1lV$WP<7?rClj>j2XnZ}Q`0a%9x05PAFRTB)s{Z?i=I>kD zf3B<{v+C^3>V$SDwFo`SR8K51$xse$oI% zlBbJfNCji=1?J*rM+ue(-MP0bvu|&EYnJ-vzy00!NpF76l-3pg$|-E<=F+pgPIQB2 z>GKYcD^=+xudb|Ct+*1HCmDOMy83Rc%%&5sHNt*&YE1og(C%q?r=`}RkhNhe4}I@# zaB`R_tG{HQ{*5ILZ+CpiS+_co_rjiAA3vAfi+IBm3U+`=Mk83Ml8`0XfZQHhL+jhq@X$Ie(bLOTQWGCuPe%IeG|3e=D zg9bDgnoGm95?7(ZMF`D4SSutjWeU)DCk1(1qNwnrvrw(e z6vA31V@-mV3Z0H7h3hjFmWyHf$S1;N)b5L>!cJ$QM*EHrONEp+hYF~au#bh&D0f^r z>~&IDO2sg~O-JvIMAYty^bqFP-bi5`ZL5_6RNBbVAajMtY*iBU=L?{Jo`$Xq@yK4w zMmoz`xcSD~Xx1l|G70Q8L|X48sh3ii%w8>lrC0>h`*f)9CZYX!49?C7ros)*!XM@j z4}|)0D$H2|)*M=ey;edboTx?uTcw!DgfM6Fpna5ruJiF|+!cwr|N2wm_wEz>#MNaH z&^}Iq=>v~7iB@7Q6*8!(KUVDM$ oHrO?UOoEOhlY8fVgB?sh0XTn_nkWgd9RL6T07*qoM6N<$f_mL}CIA2c literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/whistling.png b/public/assets/img/emoticons/whistling.png new file mode 100644 index 0000000000000000000000000000000000000000..3940f0d8215e74f6fb86126b8005e3f95c2eea28 GIT binary patch literal 1072 zcmeAS@N?(olHy`uVBq!ia0vp^;y^6I!3-n=6T)m57?^?rd_r8q9XS5~{|_YTiK3lA z)3%2G7!bzNLly%gY3pcgwDB7F$y*w60fjeT(RZKFN&}qz-h79OxB0 z(I9YQg2<_Mp;OBh&b11j>lV4xCwyg<+_i}!*ET8LSSWROz1+R+N{{v{J)SB0c&X&$ z)iO_}3O}78@^q2J(_IQL7E8QXDg9!V#N@P4Pl`(5%McgTL+CHHZk(#HexACD@3+9UVrr0VCr@}Eztf4-#k<)Gr1 zLrP!HXnZ}a`1QE**Rv|$jw*gTru6N&(zlZ;-_EFiJFE8nl*;#Ws^2ebeZQgcR z{C}+b{|ON3|9`3nOiNj_OPhg-%%CL5FPMRWk&}y?S6oI`P2IrE!pb@zv8<`NwWG6Z z_PqHkw;eft;^gVG=dV0}`SR8K51)Sh3aaus0n`)g>Eakt!I+$Igik_3vY}ySxQkSZEI&{(v*_bC(hb$74vq@nPxyi0{>r<1c;=3#BVmACgz{uQOTWu~a;i7)D zHPhip>8bSGd)xEh&$C@E_WjG(GLg27&5cJxmDShW*qeTSU2J!_-t7&kFPG%hI&Rpp zB>nc6UhDF=w>D-Ro0ZERzFW%mMB`Cboo=h;y7#x#{(iHrb90*CMzfw!w(iZRc1+*$ z&3~@_{*_zTZCtv|;&PmXP~M#=yPu)FXV0dJT1sZF{Aadljzgixj+<&}PoAZy9A}yN z@+9|0<<`xe!46J)<}Wrhm~)UpPJfZtu3cV>PJC>VWngH2RKk-Owr3wOk{CQ){an^L HB{Ts5%ZKa> literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/wink.png b/public/assets/img/emoticons/wink.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1a9cc0725bef494cd960fcff0ad294e44444c0 GIT binary patch literal 791 zcmV+y1L*vTP)*z?q>i6$z6;)!IZ!Aj-g@-LFr>bhh~7*Y9L*Zo8`S9jTY`UHiNnL! zg@V5!#D@t3h!ctK?_Y?Ay;BR%paI?yMo-szto3T)YF1;pNQSQ-j{<+YnF-iWsJ-LT z;DyFNBrJk&!T|rY5tuOn(7YiD6;AP-wQ%OYfkL zLc1jbCg?pwE%f%0uwG8W6pVZp%fbbr&|F9 z$R{y-gl!{fVjk!X;rL$3Hls9ggKDg`DPS*^VDXPcG`$+d!bYL+?X*|pcue5w-IR_A zBx8(*WE%K~)bMmE;i{FvoSlrh@8j_617R$DLnu7w@#{ljE)>JvDCh356#fx4Nj62p zYWN0NrhvOa25W%`i@zse;-hFhdSx&R&)&VTfB5&gXjltHaMj7UODtvJ?NPD_Yb|oP zYNW8{i?H-(0%kss!LRp)vg~_y2!)J}w$WnwTQM-x%-KuCa8^lJ!0A_qAMOAE002ovPDHLkV1k#Le$W5_ literal 0 HcmV?d00001 diff --git a/public/assets/img/emoticons/wub.png b/public/assets/img/emoticons/wub.png new file mode 100644 index 0000000000000000000000000000000000000000..d5faa2b605b5d4890adb314da8754dd360d5c8f4 GIT binary patch literal 1010 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz5!3-qlM(p{{z`!IK;1l8+uE6pC|9>D!3ly!u z1vEBFfiqf(J6aLMh9GAFg=_`BY$btQCBa-Zkvs+dJZq%I_Z6M^85I-53#8q;!{6l=6y^^|Cp5dF}v_%PQj=0iqEN8pHs6xXXJj) z%KuzY@+B+(OMc1Mto*Mz1z&RtzvdQx%`5umlkg2l7M6aisQyu0_M^P&M@7|-%IaSg zRlh2#e*qblHNUEAe$~|es;T=`TlcH3{#QK^HT-G-qQ>9VHNR_Wf7jOi2C^HQem6D! zt*`&v(D1jR@o!_(Um&-+`EOg>-;VZwP0jzBfvDwQOUu8uwtpQR|2jMVb#?u3ZTa8Y z`o9f`+Wxn<{qJc1-`VlMs}qE^Zk$bptaCE9<7_*7lCh zuG#bEA3t&O)Y}a<<`Q^X{z3V#8tKV;^zI*8DYq15BCW_8~exJ>DgF92RBg4McxwqqUZ*K#l zZF$$X^~T>=a_z|b!)NXrZZH1S+3}%plktBO?e8s3v(`R+b;qK7wYkw|Uax3X+;aei5s`ykzeTk@1Jb^;>%g4TJKDI%F}i!mH5oD+4r<#sgPhd z!)4pvk9-`udq1abJHJjtgy}#?e0JM()qj(GD^A|{^XuKaTX!(;E(mlhY6&R4bm?dtl|a{u%{|NCv2Gos23oiZL50^^6l)78&q Iol`;+01z+H<^TWy literal 0 HcmV?d00001 diff --git a/public/assets/img/logo-lg-white.png b/public/assets/img/logo-lg-white.png deleted file mode 100644 index 8af573cc9385d47842991f96bf30adbd09f53a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20646 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 Y(XlVyEVDxYk|2Ax>u1G(@j3I~0d=yQga7~l diff --git a/public/assets/js/jquery.wysibb.min.js b/public/assets/js/jquery.wysibb.min.js new file mode 100644 index 0000000..f92b2d8 --- /dev/null +++ b/public/assets/js/jquery.wysibb.min.js @@ -0,0 +1,5 @@ +/*! WysiBB v1.5.1 2014-03-26 + Author: Vadim Dobroskok + */ +"undefined"==typeof WBBLANG&&(WBBLANG={}),WBBLANG.en=CURLANG={bold:"Bold",italic:"Italic",underline:"Underline",strike:"Strike",link:"Link",img:"Insert image",sup:"Superscript",sub:"Subscript",justifyleft:"Align left",justifycenter:"Align center",justifyright:"Align right",table:"Insert table",bullist:"• Unordered list",numlist:"1. Ordered list",quote:"Quote",offtop:"Offtop",code:"Code",spoiler:"Spoiler",fontcolor:"Font color",fontsize:"Font size",fontfamily:"Font family",fs_verysmall:"Very small",fs_small:"Small",fs_normal:"Normal",fs_big:"Big",fs_verybig:"Very big",smilebox:"Insert emoticon",video:"Insert YouTube",removeFormat:"Remove Format",modal_link_title:"Insert link",modal_link_text:"Display text",modal_link_url:"URL",modal_email_text:"Display email",modal_email_url:"Email",modal_link_tab1:"Insert URL",modal_img_title:"Insert image",modal_img_tab1:"Insert URL",modal_img_tab2:"Upload image",modal_imgsrc_text:"Enter image URL",modal_img_btn:"Choose file",add_attach:"Add Attachment",modal_video_text:"Enter the URL of the video",close:"Close",save:"Save",cancel:"Cancel",remove:"Delete",validation_err:"The entered data is invalid",error_onupload:"Error during file upload",fileupload_text1:"Drop file here",fileupload_text2:"or",loading:"Loading",auto:"Auto",views:"Views",downloads:"Downloads",sm1:"Smile",sm2:"Laughter",sm3:"Wink",sm4:"Thank you",sm5:"Scold",sm6:"Shock",sm7:"Angry",sm8:"Pain",sm9:"Sick"},wbbdebug=!0,function(a){"use strict";a.wysibb=function(b,c){a(b).data("wbb",this),c&&c.deflang&&"undefined"!=typeof WBBLANG[c.deflang]&&(CURLANG=WBBLANG[c.deflang]),c&&c.lang&&"undefined"!=typeof WBBLANG[c.lang]&&(CURLANG=WBBLANG[c.lang]),this.txtArea=b,this.$txtArea=a(b);this.$txtArea.attr("id")||this.setUID(this.txtArea);this.options={bbmode:!1,onlyBBmode:!1,themeName:"default",bodyClass:"",lang:"ru",tabInsert:!0,imgupload:!1,img_uploadurl:"/iupload.php",img_maxwidth:800,img_maxheight:800,hotkeys:!0,showHotkeys:!0,autoresize:!0,resize_maxheight:800,loadPageStyles:!0,traceTextarea:!0,smileConversion:!0,buttons:"bold,italic,underline,strike,sup,sub,|,img,video,link,|,bullist,numlist,|,fontcolor,fontsize,fontfamily,|,justifyleft,justifycenter,justifyright,|,quote,code,table,removeFormat",allButtons:{bold:{title:CURLANG.bold,buttonHTML:'',excmd:"bold",hotkey:"ctrl+b",transform:{"{SELTEXT}":"[b]{SELTEXT}[/b]","{SELTEXT}":"[b]{SELTEXT}[/b]"}},italic:{title:CURLANG.italic,buttonHTML:'',excmd:"italic",hotkey:"ctrl+i",transform:{"{SELTEXT}":"[i]{SELTEXT}[/i]","{SELTEXT}":"[i]{SELTEXT}[/i]"}},underline:{title:CURLANG.underline,buttonHTML:'',excmd:"underline",hotkey:"ctrl+u",transform:{"{SELTEXT}":"[u]{SELTEXT}[/u]"}},strike:{title:CURLANG.strike,buttonHTML:'',excmd:"strikeThrough",transform:{"{SELTEXT}":"[s]{SELTEXT}[/s]","{SELTEXT}":"[s]{SELTEXT}[/s]"}},sup:{title:CURLANG.sup,buttonHTML:'',excmd:"superscript",transform:{"{SELTEXT}":"[sup]{SELTEXT}[/sup]"}},sub:{title:CURLANG.sub,buttonHTML:'',excmd:"subscript",transform:{"{SELTEXT}":"[sub]{SELTEXT}[/sub]"}},link:{title:CURLANG.link,buttonHTML:'',hotkey:"ctrl+shift+2",modal:{title:CURLANG.modal_link_title,width:"500px",tabs:[{input:[{param:"SELTEXT",title:CURLANG.modal_link_text,type:"div"},{param:"URL",title:CURLANG.modal_link_url,validation:"^http(s)?://"}]}]},transform:{'{SELTEXT}':"[url={URL}]{SELTEXT}[/url]",'{URL}':"[url]{URL}[/url]"}},img:{title:CURLANG.img,buttonHTML:'',hotkey:"ctrl+shift+1",addWrap:!0,modal:{title:CURLANG.modal_img_title,width:"600px",tabs:[{title:CURLANG.modal_img_tab1,input:[{param:"SRC",title:CURLANG.modal_imgsrc_text,validation:"^http(s)?://.*?.(jpg|png|gif|jpeg)$"}]}],onLoad:this.imgLoadModal},transform:{'':"[img]{SRC}[/img]",'':"[img width={WIDTH},height={HEIGHT}]{SRC}[/img]"}},bullist:{title:CURLANG.bullist,buttonHTML:'',excmd:"insertUnorderedList",transform:{"
      {SELTEXT}
    ":"[list]{SELTEXT}[/list]","
  • {SELTEXT}
  • ":"[*]{SELTEXT}[/*]"}},numlist:{title:CURLANG.numlist,buttonHTML:'',excmd:"insertOrderedList",transform:{"
      {SELTEXT}
    ":"[list=1]{SELTEXT}[/list]","
  • {SELTEXT}
  • ":"[*]{SELTEXT}[/*]"}},quote:{title:CURLANG.quote,buttonHTML:'',hotkey:"ctrl+shift+3",transform:{"
    {SELTEXT}
    ":"[quote]{SELTEXT}[/quote]"}},code:{title:CURLANG.code,buttonText:"[code]",hotkey:"ctrl+shift+4",onlyClearText:!0,transform:{"{SELTEXT}":"[code]{SELTEXT}[/code]"}},offtop:{title:CURLANG.offtop,buttonText:"offtop",transform:{'{SELTEXT}':"[offtop]{SELTEXT}[/offtop]"}},fontcolor:{type:"colorpicker",title:CURLANG.fontcolor,excmd:"foreColor",valueBBname:"color",subInsert:!0,colors:"#000000,#444444,#666666,#999999,#b6b6b6,#cccccc,#d8d8d8,#efefef,#f4f4f4,#ffffff,-, #ff0000,#980000,#ff7700,#ffff00,#00ff00,#00ffff,#1e84cc,#0000ff,#9900ff,#ff00ff,-, #f4cccc,#dbb0a7,#fce5cd,#fff2cc,#d9ead3,#d0e0e3,#c9daf8,#cfe2f3,#d9d2e9,#ead1dc, #ea9999,#dd7e6b,#f9cb9c,#ffe599,#b6d7a8,#a2c4c9,#a4c2f4,#9fc5e8,#b4a7d6,#d5a6bd, #e06666,#cc4125,#f6b26b,#ffd966,#93c47d,#76a5af,#6d9eeb,#6fa8dc,#8e7cc3,#c27ba0, #cc0000,#a61c00,#e69138,#f1c232,#6aa84f,#45818e,#3c78d8,#3d85c6,#674ea7,#a64d79, #900000,#85200C,#B45F06,#BF9000,#38761D,#134F5C,#1155Cc,#0B5394,#351C75,#741B47, #660000,#5B0F00,#783F04,#7F6000,#274E13,#0C343D,#1C4587,#073763,#20124D,#4C1130",transform:{'{SELTEXT}':"[color={COLOR}]{SELTEXT}[/color]"}},table:{type:"table",title:CURLANG.table,cols:10,rows:10,cellwidth:20,transform:{"{SELTEXT}":"[td]{SELTEXT}[/td]","{SELTEXT}":"[tr]{SELTEXT}[/tr]",'{SELTEXT}
    ':"[table]{SELTEXT}[/table]"},skipRules:!0},fontsize:{type:"select",title:CURLANG.fontsize,options:"fs_verysmall,fs_small,fs_normal,fs_big,fs_verybig"},fontfamily:{type:"select",title:CURLANG.fontfamily,excmd:"fontName",valueBBname:"font",options:[{title:"Arial",exvalue:"Arial"},{title:"Comic Sans MS",exvalue:"Comic Sans MS"},{title:"Courier New",exvalue:"Courier New"},{title:"Georgia",exvalue:"Georgia"},{title:"Lucida Sans Unicode",exvalue:"Lucida Sans Unicode"},{title:"Tahoma",exvalue:"Tahoma"},{title:"Times New Roman",exvalue:"Times New Roman"},{title:"Trebuchet MS",exvalue:"Trebuchet MS"},{title:"Verdana",exvalue:"Verdana"}],transform:{'{SELTEXT}':"[font={FONT}]{SELTEXT}[/font]"}},smilebox:{type:"smilebox",title:CURLANG.smilebox,buttonHTML:''},justifyleft:{title:CURLANG.justifyleft,buttonHTML:'',groupkey:"align",transform:{'

    {SELTEXT}

    ':"[left]{SELTEXT}[/left]"}},justifyright:{title:CURLANG.justifyright,buttonHTML:'',groupkey:"align",transform:{'

    {SELTEXT}

    ':"[right]{SELTEXT}[/right]"}},justifycenter:{title:CURLANG.justifycenter,buttonHTML:'',groupkey:"align",transform:{'

    {SELTEXT}

    ':"[center]{SELTEXT}[/center]"}},video:{title:CURLANG.video,buttonHTML:'',modal:{title:CURLANG.video,width:"600px",tabs:[{title:CURLANG.video,input:[{param:"SRC",title:CURLANG.modal_video_text}]}],onSubmit:function(a){var b=this.$modal.find('input[name="SRC"]').val();b&&(b=b.replace(/^\s+/,"").replace(/\s+$/,""));var c;if(c=b.match(-1!=b.indexOf("youtu.be")?/^http[s]*:\/\/youtu\.be\/([a-z0-9_-]+)/i:/^http[s]*:\/\/www\.youtube\.com\/watch\?.*?v=([a-z0-9_-]+)/i),c&&2==c.length){var d=c[1];this.insertAtCursor(this.getCodeByCommand(a,{src:d}))}return this.closeModal(),this.updateUI(),!1}},transform:{'':"[video]{SRC}[/video]"}},fs_verysmall:{title:CURLANG.fs_verysmall,buttonText:"fs1",excmd:"fontSize",exvalue:"1",transform:{'{SELTEXT}':"[size=50]{SELTEXT}[/size]"}},fs_small:{title:CURLANG.fs_small,buttonText:"fs2",excmd:"fontSize",exvalue:"2",transform:{'{SELTEXT}':"[size=85]{SELTEXT}[/size]"}},fs_normal:{title:CURLANG.fs_normal,buttonText:"fs3",excmd:"fontSize",exvalue:"3",transform:{'{SELTEXT}':"[size=100]{SELTEXT}[/size]"}},fs_big:{title:CURLANG.fs_big,buttonText:"fs4",excmd:"fontSize",exvalue:"4",transform:{'{SELTEXT}':"[size=150]{SELTEXT}[/size]"}},fs_verybig:{title:CURLANG.fs_verybig,buttonText:"fs5",excmd:"fontSize",exvalue:"6",transform:{'{SELTEXT}':"[size=200]{SELTEXT}[/size]"}},removeformat:{title:CURLANG.removeFormat,buttonHTML:'',excmd:"removeFormat"}},systr:{"
    ":"\n",'{SELTEXT}':" {SELTEXT}"},customRules:{td:[["[td]{SELTEXT}[/td]",{seltext:{rgx:!1,attr:!1,sel:!1}}]],tr:[["[tr]{SELTEXT}[/tr]",{seltext:{rgx:!1,attr:!1,sel:!1}}]],table:[["[table]{SELTEXT}[/table]",{seltext:{rgx:!1,attr:!1,sel:!1}}]]},smileList:[],attrWrap:["src","color","href"]},this.inited=this.options.onlyBBmode,this.options.themePrefix||a("link").each(a.proxy(function(b,c){var d=a(c).get(0).href.match(/(.*\/)(.*)\/wbbtheme\.css.*$/);null!==d&&(this.options.themeName=d[2],this.options.themePrefix=d[1])},this)),"undefined"!=typeof WBBPRESET&&(WBBPRESET.allButtons&&a.each(WBBPRESET.allButtons,a.proxy(function(a,b){b.transform&&this.options.allButtons[a]&&delete this.options.allButtons[a].transform},this)),a.extend(!0,this.options,WBBPRESET)),c&&c.allButtons&&a.each(c.allButtons,a.proxy(function(a,b){b.transform&&this.options.allButtons[a]&&delete this.options.allButtons[a].transform},this)),a.extend(!0,this.options,c),this.init()},a.wysibb.prototype={lastid:1,init:function(){a.log("Init",this),this.isMobile=function(a){/android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|meego.+mobile|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)}(navigator.userAgent||navigator.vendor||window.opera),this.options.onlyBBmode===!0&&(this.options.bbmode=!0),this.controllers=[],this.options.buttons=this.options.buttons.toLowerCase(),this.options.buttons=this.options.buttons.split(","),this.options.allButtons._systr={},this.options.allButtons._systr.transform=this.options.systr,this.smileFind(),this.initTransforms(),this.build(),this.initModal(),this.options.hotkeys!==!0||this.isMobile||this.initHotkeys(),this.options.smileList&&this.options.smileList.length>0&&this.options.smileList.sort(function(a,b){return b.bbcode.length-a.bbcode.length}),this.$txtArea.parents("form").bind("submit",a.proxy(function(){return this.sync(),!0},this)),this.$txtArea.parents("form").find("input[id*='preview'],input[id*='submit'],input[class*='preview'],input[class*='submit'],input[name*='preview'],input[name*='submit']").bind("mousedown",a.proxy(function(){this.sync(),setTimeout(a.proxy(function(){this.options.bbmode===!1&&this.$txtArea.removeAttr("wbbsync").val("")},this),1e3)},this)),this.options.initCallback&&this.options.initCallback.call(this),a.log(this)},initTransforms:function(){a.log("Create rules for transform HTML=>BB");var b=this.options;b.rules||(b.rules={}),b.groups||(b.groups={});var c=b.buttons.slice();c.push("_systr");for(var d=0;d-1||a(d).parent().contents().size()>1){var n=a("").html("{"+i+"}");this.setUID(n,"wbb");var p=f.indexOf(i)+i.length+1,q=f.substr(p,f.length-p);d.data=f.substr(0,f.indexOf(i)-1),a(d).after(this.elFromString(q,document)).after(n),m=(m?m+" ":"")+this.filterByNode(n),k=!1}o[i.toLowerCase()]={sel:m,attr:!1,rgx:k},e[e.length]=m}},this)),e=null},this));var p=k.html();p=this.unwrapAttrs(p),i!=p&&(delete e.transform[i],e.transform[p]=j,h=p)}b.rules[l].push([j,o]),e.onlyClearText===!0&&(this.cleartext||(this.cleartext={}),this.cleartext[l]=c[d]),e.groupkey&&(b.groups[e.groupkey]||(b.groups[e.groupkey]=[]),b.groups[e.groupkey].push(l))}}e.rootSelector&&this.sortArray(e.rootSelector,-1);var q=a.map(e.transform,function(a,b){return b}).sort(function(a,b){return(b[0]||"").length-(a[0]||"").length});e.bbcode=e.transform[q[0]],e.html=q[0]}}}this.options.btnlist=c,a.extend(b.rules,this.options.customRules),b.srules={},this.options.smileList&&a.each(b.smileList,a.proxy(function(c,d){var e=a(this.strf(d.img,b)),f=this.filterByNode(e);b.srules[f]=[d.bbcode,d.img]},this));for(var r in b.rules)this.options.rules[r].sort(function(a,b){return b[0].length-a[0].length});this.rsellist=[];for(var r in this.options.rules)this.rsellist.push(r);this.sortArray(this.rsellist,-1)},build:function(){if(a.log("Build editor"),this.$editor=a("
    ").addClass("wysibb"),this.isMobile&&this.$editor.addClass("wysibb-mobile"),this.options.direction&&this.$editor.css("direction",this.options.direction),this.$editor.insertAfter(this.txtArea).append(this.txtArea),this.startHeight=this.$txtArea.outerHeight(),this.$txtArea.addClass("wysibb-texarea"),this.buildToolbar(),this.$txtArea.wrap('
    '),this.options.onlyBBmode===!1){var b=this.options.minheight||this.$txtArea.outerHeight(),c=(this.options.resize_maxheight,this.options.autoresize===!0?this.options.resize_maxheight:b);if(this.$body=a(this.strf('
    ',{maxheight:c,height:b})).insertAfter(this.$txtArea),this.body=this.$body[0],this.$txtArea.hide(),b>32&&this.$toolbar.css("max-height",b),a.log("WysiBB loaded"),this.$body.addClass("wysibb-body").addClass(this.options.bodyClass),this.options.direction&&this.$body.css("direction",this.options.direction),"contentEditable"in this.body){this.body.contentEditable=!0;try{document.execCommand("StyleWithCSS",!1,!1),this.$body.append("")}catch(d){}}else this.options.onlyBBmode=this.options.bbmode=!0;this.txtArea.value.length>0&&this.txtAreaInitContent(),this.$body.bind("keydown",a.proxy(function(b){return 86==b.which&&(1==b.ctrlKey||1==b.metaKey)||45==b.which&&(1==b.shiftKey||1==b.metaKey)?(this.$pasteBlock||(this.saveRange(),this.$pasteBlock=a(this.elFromString('
    
    ')),this.$pasteBlock.appendTo(this.body),setTimeout(a.proxy(function(){this.clearPaste(this.$pasteBlock);var b=""+this.$pasteBlock.html()+"";this.$body.attr("contentEditable","true"),this.$pasteBlock.blur().remove(),this.body.focus(),this.cleartext&&(a.log("Check if paste to clearText Block"),this.isInClearTextBlock()&&(b=this.toBB(b).replace(/\n/g,"
    ").replace(/\s{3}/g,''))),b=b.replace(/\t/g,''),this.selectRange(this.lastRange),this.insertAtCursor(b,!1),this.lastRange=!1,this.$pasteBlock=!1},this),1),this.selectNode(this.$pasteBlock[0])),!0):void 0},this)),this.$body.bind("keydown",a.proxy(function(a){if(13==a.which){var b=this.isContain(this.getSelectNode(),"li");b||(a.preventDefault&&a.preventDefault(),this.checkForLastBR(this.getSelectNode()),this.insertAtCursor("
    ",!1))}},this)),this.options.tabInsert===!0&&this.$body.bind("keydown",a.proxy(this.pressTab,this)),this.$body.bind("mouseup keyup",a.proxy(this.updateUI,this)),this.$body.bind("mousedown",a.proxy(function(a){this.clearLastRange(),this.checkForLastBR(a.target)},this)),this.options.traceTextarea===!0&&(a(document).bind("mousedown",a.proxy(this.traceTextareaEvent,this)),this.$txtArea.val("")),this.options.hotkeys===!0&&this.$body.bind("keydown",a.proxy(this.presskey,this)),this.options.smileConversion===!0&&this.$body.bind("keyup",a.proxy(this.smileConversion,this)),this.inited=!0,this.options.autoresize===!0&&(this.$bresize=a(this.elFromString('
    ')).appendTo(this.$editor).wdrag({scope:this,axisY:!0,height:b})),this.imgListeners()}this.$txtArea.bind("mouseup keyup",a.proxy(function(){clearTimeout(this.uitimer),this.uitimer=setTimeout(a.proxy(this.updateUI,this),100)},this)),this.options.hotkeys===!0&&a(document).bind("keydown",a.proxy(this.presskey,this))},buildToolbar:function(){if(this.options.toolbar===!1)return!1;this.$toolbar=a("
    ").addClass("wysibb-toolbar").prependTo(this.$editor);var b;a.each(this.options.buttons,a.proxy(function(c,d){var e=this.options.allButtons[d];(0==c||"|"==d||"-"==d)&&("-"==d&&this.$toolbar.append("
    "),b=a('
    ').appendTo(this.$toolbar)),e&&("colorpicker"==e.type?this.buildColorpicker(b,d,e):"table"==e.type?this.buildTablepicker(b,d,e):"select"==e.type?this.buildSelect(b,d,e):"smilebox"==e.type?this.buildSmilebox(b,d,e):this.buildButton(b,d,e))},this)),this.$toolbar.find(".btn-tooltip").hover(function(){a(this).parent().css("overflow","hidden")},function(){a(this).parent().css("overflow","visible")});var c=a(document.createElement("div")).addClass("wysibb-toolbar-container modeSwitch").html('
    [bbcode]
    ').appendTo(this.$toolbar);1==this.options.bbmode&&c.children(".wysibb-toolbar-btn").addClass("on"),this.options.onlyBBmode===!1&&c.children(".wysibb-toolbar-btn").click(a.proxy(function(b){a(b.currentTarget).toggleClass("on"),this.modeSwitch()},this))},buildButton:function(b,c,d){"object"!=typeof b&&(b=this.$toolbar);var e=d.buttonHTML?a(this.strf(d.buttonHTML,this.options)).addClass("btn-inner"):this.strf('{text}',{text:d.buttonText.replace(/['+d.hotkey+"]":"",g=a('
    ').appendTo(b).append(e).append(this.strf('{title}{hotkey}',{title:d.title,hotkey:f}));this.controllers.push(g),g.bind("queryState",a.proxy(function(b){this.queryState(c)?a(b.currentTarget).addClass("on"):a(b.currentTarget).removeClass("on")},this)),g.mousedown(a.proxy(function(b){b.preventDefault(),this.execCommand(c,d.exvalue||!1),a(b.currentTarget).trigger("queryState")},this))},buildColorpicker:function(b,c,d){var e=a('
    ').appendTo(b).append('
    ').append(this.strf('{title}',{title:d.title})),f=e.find(".cp-line"),g=a('
    ').appendTo(e);g.append('
    '+CURLANG.auto+"
    ");for(var h=d.colors?d.colors.split(","):[],i=0;i':this.strf('
    ',{color:h[i]}));var j=a(document.body).css("color");this.controllers.push(e),e.bind("queryState",a.proxy(function(){f.css("background-color",j);var a=this.queryState(c,!0);a&&(f.css("background-color",this.options.bbmode?a.color:a),e.find(".ve-tlb-colorpick span.fonticon").css("color",this.options.bbmode?a.color:a))},this)),e.mousedown(a.proxy(function(a){a.preventDefault(),this.dropdownclick(".wbb-cp",".wbb-list",a)},this)),e.find(".sc").mousedown(a.proxy(function(b){b.preventDefault(),this.selectLastRange();var d=a(b.currentTarget).attr("title");this.execCommand(c,d),e.trigger("queryState")},this)),e.find(".nc").mousedown(a.proxy(function(a){a.preventDefault(),this.selectLastRange(),this.execCommand(c,j),e.trigger("queryState")},this)),e.mousedown(function(a){a.preventDefault&&a.preventDefault()})},buildTablepicker:function(b,c,d){var e=a('
    ').appendTo(b).append('').append(this.strf('{title}',{title:d.title})),f=a('
    ').appendTo(e),g=a("
    ").css({position:"relative","box-sizing":"border-box"}).appendTo(f),h=d.rows||10,i=d.cols||10,j=h*i;g.css("height",h*d.cellwidth+2+"px");for(var k=1;i>=k;k++)for(var l=1;h>=l;l++){var m='
    ';g.append(m)}e.find(".tbl-sel").mousedown(a.proxy(function(b){b.preventDefault();for(var c=a(b.currentTarget).attr("title"),d=c.split(","),e=this.options.bbmode?"[table]":'',f=1;f<=d[0];f++){e+=this.options.bbmode?" [tr]\n":"";for(var g=1;g<=d[1];g++)e+=this.options.bbmode?" [td][/td]\n":"";e+=this.options.bbmode?"[/tr]\n":""}e+=this.options.bbmode?"[/table]":"
    
    ",this.insertAtCursor(e)},this)),e.mousedown(a.proxy(function(a){a.preventDefault(),this.dropdownclick(".wbb-tbl",".wbb-list",a)},this))},buildSelect:function(b,c,d){for(var e=a('
    ').appendTo(b).append(this.strf('{title}',d)).append(this.strf('{title}',{title:d.title})),f=a('
    ').appendTo(e),g=e.find("span.val"),h=a.isArray(d.options)?d.options:d.options.split(","),i=this.isMobile?a("
    ',c))},this))},this)),a.isFunction(c.onLoad)&&c.onLoad.call(this,b,c,d),f.find("#wbbm-submit").click(a.proxy(function(){if(a.isFunction(c.onSubmit)){var e=c.onSubmit.call(this,b,c,d);if(e===!1)return}var f={},g=!0;this.$modal.find(".wbbm-inperr").remove(),this.$modal.find(".wbbm-brdred").removeClass("wbbm-brdred"),a.each(this.$modal.find(".tab-cont:visible .inp-text"),a.proxy(function(b,d){var e=a(d).parents(".tab-cont").attr("tid"),h=a(d).attr("name").toLowerCase(),i="";i=a(d).is("input,textrea,select")?a(d).val():a(d).html();var j=c.tabs[e].input[b].validation;"undefined"!=typeof j&&(i.match(new RegExp(j,"i"))||(g=!1,a(d).after(''+CURLANG.validation_err+"").addClass("wbbm-brdred"))),f[h]=i},this)),g&&(a.log("Last range: "+this.lastRange),this.selectLastRange(),d&&this.wbbRemoveCallback(b,!0),this.wbbInsertCallback(b,f),this.closeModal(),this.updateUI())},this)),f.find("#wbbm-remove").click(a.proxy(function(){this.selectLastRange(),this.wbbRemoveCallback(b),this.closeModal(),this.updateUI()},this)),a(document.body).css("overflow","hidden"),a("body").height()>a(window).height()&&a(document.body).css("padding-right","18px"),this.$modal.show(),this.isMobile?f.css("margin-top","10px"):f.css("margin-top",(a(window).height()-f.outerHeight())/3+"px"),setTimeout(a.proxy(function(){this.$modal.find(".inp-text:visible")[0].focus()},this),10)},escModal:function(a){27==a.which&&this.closeModal()},closeModal:function(){return a(document.body).css("overflow","auto").css("padding-right","0").unbind("keyup",this.escModal),this.$modal.find("#wbbm-submit,#wbbm-remove").unbind("click"),this.$modal.hide(),this.lastRange=!1,this},getParams:function(b,c,d){var e={};if(this.options.bbmode){var f=c.match(/\{[\s\S]+?\}/g);c=this.prepareRGX(c);var g=new RegExp(c,"g"),h=this.txtArea.value;d>0&&(h=h.substr(d,h.length-d));var i=g.exec(h);i&&a.each(f,function(a,b){e[b.replace(/\{|\}/g,"").replace(/"/g,"'").toLowerCase()]=i[a+1]})}else{var j=this.options.rules[c][0][1];a.each(j,a.proxy(function(c,d){var f="",g=d.sel!==!1?f=a(b).find(d.sel):a(b);if(f=d.attr!==!1?g.attr(d.attr):g.html()){if(d.rgx!==!1){var h=f.match(new RegExp(d.rgx));h&&2==h.length&&(f=h[1])}e[c]=f.replace(/"/g,"'")}},this))}return e},imgLoadModal:function(){a.log("imgLoadModal"),this.options.imgupload===!0?(this.$modal.find("#imguploader").dragfileupload({url:this.strf(this.options.img_uploadurl,this.options),extraParams:{maxwidth:this.options.img_maxwidth,maxheight:this.options.img_maxheight},themePrefix:this.options.themePrefix,themeName:this.options.themeName,success:a.proxy(function(a){this.$txtArea.insertImage(a.image_link,a.thumb_link),this.closeModal(),this.updateUI()},this)}),this.$modal.find("#fileupl").bind("change",function(){a("#fupform").submit()}),this.$modal.find("#fupform").bind("submit",a.proxy(function(b){a(b.target).parents("#imguploader").hide().after('

    '+CURLANG.loading+"
    ").parent().css("text-align","center")},this))):(this.$modal.find(".hastabs").removeClass("hastabs"),this.$modal.find("#imguploader").parents(".tab-cont").remove(),this.$modal.find(".wbbm-tablist").remove())},imgSubmitModal:function(){a.log("imgSubmitModal")},printObjectInIE:function(b){try{a.log(JSON.stringify(b))}catch(c){}},checkFilter:function(b,c){a.log("node: "+a(b).get(0).outerHTML+" filter: "+c+" res: "+a(b).is(c.toLowerCase()))},debug:function(b){if(this.options.debug===!0){var c=(new Date).getTime();"undefined"!=typeof console?console.log(c-this.startTime+" ms: "+b):a("#exlog").append("

    "+(c-this.startTime)+" ms: "+b+"

    "),this.startTime=c}},isChrome:function(){return window.chrome?!0:!1},fixTableTransform:function(b){return b?-1==a.inArray("table",this.options.buttons)?b.replace(/\<(\/*?(table|tr|td|tbody))[^>]*\>/gi,""):b.replace(/\<(\/*?(table|tr|td))[^>]*\>/gi,"[$1]".toLowerCase()).replace(/\<\/*tbody[^>]*\>/gi,""):""}},a.log=function(b){"undefined"!=typeof wbbdebug&&wbbdebug===!0&&("undefined"!=typeof console?console.log(b):a("#exlog").append("

    "+b+"

    "))},a.fn.wysibb=function(b){return this.each(function(){var c=a(this).data("wbb");c||new a.wysibb(this,b)})},a.fn.wdrag=function(b){b.scope||(b.scope=this);var c,d={x:0,y:0,height:0};b.scope.drag_mousedown=function(e){e.preventDefault(),d={x:e.pageX,y:e.pageY,height:b.height,sheight:b.scope.$body.height()},c=!0,a(document).bind("mousemove",a.proxy(b.scope.drag_mousemove,this)),a(this).addClass("drag")},b.scope.drag_mouseup=function(d){c===!0&&(d.preventDefault(),a(document).unbind("mousemove",b.scope.drag_mousemove),a(this).removeClass("drag"),c=!1)},b.scope.drag_mousemove=function(a){a.preventDefault();var c=0,e=0;if(b.axisX&&(c=a.pageX-d.x),b.axisY&&(e=a.pageY-d.y),0!=e){var f=d.sheight+e;f>d.height&&f<=b.scope.options.resize_maxheight&&(1==b.scope.options.bbmode?b.scope.$txtArea.css(b.scope.options.autoresize===!0?"min-height":"height",f+"px"):b.scope.$body.css(b.scope.options.autoresize===!0?"min-height":"height",f+"px"))}},a(this).bind("mousedown",b.scope.drag_mousedown),a(document).bind("mouseup",a.proxy(b.scope.drag_mouseup,this))},a.fn.getDoc=function(){return this.data("wbb").doc},a.fn.getSelectText=function(a){return this.data("wbb").getSelectText(a)},a.fn.bbcode=function(a){return"undefined"!=typeof a?(this.data("wbb").options.bbmode?this.data("wbb").$txtArea.val(a):this.data("wbb").$body.html(this.data("wbb").getHTML(a)),this):this.data("wbb").getBBCode()},a.fn.htmlcode=function(a){return this.data("wbb").options.onlyBBMode||this.data("wbb").inited!==!0?void 0:"undefined"!=typeof a?(this.data("wbb").$body.html(a),this):this.data("wbb").getHTML(this.data("wbb").$txtArea.val())},a.fn.getBBCode=function(){return this.data("wbb").getBBCode()},a.fn.getHTML=function(){var a=this.data("wbb");return a.getHTML(a.$txtArea.val())},a.fn.getHTMLByCommand=function(a,b){return this.data("wbb").getHTMLByCommand(a,b)},a.fn.getBBCodeByCommand=function(a,b){return this.data("wbb").getBBCodeByCommand(a,b)},a.fn.insertAtCursor=function(a,b){return this.data("wbb").insertAtCursor(a,b),this.data("wbb")},a.fn.execCommand=function(a,b){return this.data("wbb").execCommand(a,b),this.data("wbb")},a.fn.insertImage=function(a,b){var c=this.data("wbb"),d=b?c.getCodeByCommand("link",{url:a,seltext:c.getCodeByCommand("img",{src:b})}):c.getCodeByCommand("img",{src:a});return this.insertAtCursor(d),c},a.fn.sync=function(){return this.data("wbb").sync(),this.data("wbb")},a.fn.destroy=function(){this.data("wbb").destroy()},a.fn.queryState=function(a){return this.data("wbb").queryState(a)}}(jQuery),function(a){"use strict";function b(b,c){this.$block=a(b),this.opt=a.extend({url:!1,success:!1,extraParams:!1,fileParam:"img",validation:".(jpg|png|gif|jpeg)$",t1:CURLANG.fileupload_text1,t2:CURLANG.fileupload_text2},c)}a.fn.dragfileupload=function(a){return this.each(function(){var c=new b(this,a);c.init()})},b.prototype={init:function(){if(null!=window.FormData){this.$block.addClass("drag"),this.$block.prepend('
    '+this.opt.t2+"
    "),this.$block.prepend('
    '+this.opt.t1+"
    "),this.$block.bind("dragover",function(){return a(this).addClass("dragover"),!1}),this.$block.bind("dragleave",function(){return a(this).removeClass("dragover"),!1});var b=a.proxy(function(a){var b=parseInt(a.loaded/a.total*100,10);this.$loader.children("span").text(CURLANG.loading+": "+b+"%")},this),c=jQuery.ajaxSettings.xhr();c.upload&&c.upload.addEventListener("progress",b,!1),this.$block[0].ondrop=a.proxy(function(b){b.preventDefault(),this.$block.removeClass("dragover");var d=b.dataTransfer.files[0];if(this.opt.validation&&!d.name.match(new RegExp(this.opt.validation)))return this.error(CURLANG.validation_err),!1;var e=new FormData;e.append(this.opt.fileParam,d),this.opt.extraParams&&a.each(this.opt.extraParams,function(a,b){e.append(a,b)}),this.$loader=a('

    '+CURLANG.loading+"
    "),this.$block.html(this.$loader),a.ajax({type:"POST",url:this.opt.url,data:e,processData:!1,contentType:!1,xhr:function(){return c},dataType:"json",success:a.proxy(function(a){a&&1==a.status?this.opt.success(a):this.error(a.msg||CURLANG.error_onupload)},this),error:a.proxy(function(){this.error(CURLANG.error_onupload)},this)})},this)}},error:function(a){this.$block.find(".upl-error").remove().end().append(''+a+"").addClass("wbbm-brdred")}}}(jQuery); \ No newline at end of file diff --git a/public/assets/languages/ar.js b/public/assets/languages/ar.js new file mode 100644 index 0000000..672fe8d --- /dev/null +++ b/public/assets/languages/ar.js @@ -0,0 +1,68 @@ +/** + * @author Atramez_Zeton http://onyx-sy.net + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['ar'] = { + 'Bold': 'عريض', + 'Italic': 'مائل', + 'Underline': 'خط من الأسفل', + 'Strikethrough': 'خط في المنتصف', + 'Subscript': 'حرف منخفض', + 'Superscript': 'حرف مرتفع', + 'Align left': 'انحياز إلى اليسار', + 'Center': 'توسط', + 'Align right': 'انحياز إالى اليمين', + 'Justify': 'ملأ السطر', + 'Font Name': 'نوع الخط', + 'Font Size': 'حجم الخط', + 'Font Color': 'لون الخط', + 'Remove Formatting': 'ازالة التعديلات', + 'Cut': 'قص', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ctrl/Cmd-X متصفحك لا يدعم اوامر القص الرجاء استخدام اختصارات لوحة التحكم', + 'Copy': 'نسخ', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ctrl/Cmd-C متصفحك لا يدعم اوامر النسخ الرجاء استخدام اختصارات لوحة التحكم', + 'Paste': 'لصق', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ctrl/Cmd-V متصفحك لا يدعم اوامر اللصق الرجاء استخدام اختصارات لوحة التحكم', + 'Paste your text inside the following box:': 'قم بلصق نصّك في المربع', + 'Paste Text': 'الصق النص', + 'Bullet list': 'قائمة نقطية', + 'Numbered list': 'قائمة مرقمة', + 'Undo': 'تراجع', + 'Redo': 'تقدم', + 'Rows:': 'اسطر', + 'Cols:': 'اعمدة', + 'Insert a table': 'ادرج جدول', + 'Insert a horizontal rule': 'ادرج مسطرة افقية', + 'Code': 'كود', + 'Width (optional):': 'عرض (اختياري)', + 'Height (optional):': 'ارتفاع (اختياري)', + 'Insert an image': 'ادرج صورة', + 'E-mail:': 'بريد الكتروني', + 'Insert an email': 'ادرج بريدا الكترونيا', + 'URL:': 'وصلة موقع', + 'Insert a link': 'ادرج وصلة لموقع', + 'Unlink': 'ازالة الوصلة', + 'More': 'المزيد', + 'Insert an emoticon': 'ادرج وجها', + 'Video URL:': 'وصلة فيديو', + 'Insert': 'ادرج', + 'Insert a YouTube video': 'ادرج وصلة فيديو يوتيوب', + 'Insert current date': 'ادرج التاريخ الحالي', + 'Insert current time': 'ادرج الوقت الحالي', + 'Print': 'اطبع', + 'View source': 'اظهر المصدر', + 'Description (optional):': 'الوصف (اختياري)', + 'Enter the image URL:': 'ضع وصلة الصورة', + 'Enter the e-mail address:': 'ضع عنوان البريد الإلكتروني', + 'Enter the displayed text:': 'ضع النص الذي تريد اظهاره', + 'Enter URL:': 'ضع وصلة موقع', + 'Enter the YouTube video URL or ID:': 'ضع وصلة فيديو يوتيوب او رقم الفيديو', + 'Insert a Quote': 'ادرج اقتباسا', + 'Invalid YouTube video': 'هذا الفيديو غير صالح', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/ca.js b/public/assets/languages/ca.js new file mode 100644 index 0000000..cf0b09b --- /dev/null +++ b/public/assets/languages/ca.js @@ -0,0 +1,68 @@ +/** + * @author Fran Sobrino + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['ca'] = { + 'Bold': 'Negrita', + 'Italic': 'Cursiva', + 'Underline': 'Subratlla', + 'Strikethrough': 'Ratllar', + 'Subscript': 'Sub\u00edndice', + 'Superscript': 'Super\u00edndice', + 'Align left': 'Alinear a l\'Esquerra', + 'Center': 'Centrar', + 'Align right': 'Alinear a la dreta', + 'Justify': 'Justificar', + 'Font Name': 'Tipus de Lletra', + 'Font Size': 'Mida de Lletra', + 'Font Color': 'Color de Font', + 'Remove Formatting': 'Treure Formats', + 'Cut': 'Tallar', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-X', + 'Copy': 'Copiar', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-C', + 'Paste': 'Pegar', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Pega o texto dentro do seguinte recadro', + 'Paste Text': 'Pegar Texto', + 'Bullet list': 'Llista d\'Vinyetes', + 'Numbered list': 'Llista numerada', + 'Undo': 'Desfer', + 'Redo': 'Refer', + 'Rows:': 'Files', + 'Cols:': 'Columnes', + 'Insert a table': 'Inserir una taula', + 'Insert a horizontal rule': 'Insereix una Regla horitzontal', + 'Code': 'C\u00f3digo', + 'Width (optional):': 'Ample (Opcional)', + 'Height (optional):': 'Alçada (Opcional)', + 'Insert an image': 'Insereix una imatge', + 'E-mail:': 'Correu electrònic', + 'Insert an email': 'Insereix un Email', + 'URL:': 'URL', + 'Insert a link': 'Inserir un enllaç', + 'Unlink': 'Treure un enllaç', + 'More': 'Més', + 'Insert an emoticon': 'Inserir un emoticon', + 'Video URL:': 'URL del V\u00eddeo', + 'Insert': 'Insereix', + 'Insert a YouTube video': 'Insereix un v\u00eddeo de YouTube', + 'Insert current date': 'Insereix data actual', + 'Insert current time': 'Insereix hora actual', + 'Print': 'Imprimir', + 'View source': 'Veure C\u00f3digo', + 'Description (optional):': 'Descripci\u00f3 (Opcional):', + 'Enter the image URL:': 'Ingressar la URL de la imatge:', + 'Enter the e-mail address:': 'Ingressar el correu electr\u00f3nico:', + 'Enter the displayed text:': 'Ingressar el texto mostrat:', + 'Enter URL:': 'Entrada URL:', + 'Enter the YouTube video URL or ID:': 'Entrada URL ou ID de YouTube', + 'Insert a Quote': 'v Insereix', + 'Invalid YouTube video': 'V\u00eddeo de YouTube Inv\u00e1lido', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/cn.js b/public/assets/languages/cn.js new file mode 100644 index 0000000..6a27f0b --- /dev/null +++ b/public/assets/languages/cn.js @@ -0,0 +1,68 @@ +/** + * @author + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['cn'] = { + 'Bold': '粗体', + 'Italic': '斜体', + 'Underline': '下划线', + 'Strikethrough': '删除线', + 'Subscript': '下标', + 'Superscript': '上标', + 'Align left': '靠左对齐', + 'Center': '置中', + 'Align right': '靠右对齐', + 'Justify': '两端对齐', + 'Font Name': '字体', + 'Font Size': '字号', + 'Font Color': '字色', + 'Remove Formatting': '格式清除', + 'Cut': '剪切', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': '您的浏览器不支持剪切命令,请使用快捷键 Ctrl/Cmd-X', + 'Copy': '拷贝', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': '您的浏览器不支持拷贝命令,请使用快捷键 Ctrl/Cmd-C', + 'Paste': '粘贴', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': '您的浏览器不支持粘贴命令,请使用快捷键 Ctrl/Cmd-V', + 'Paste your text inside the following box:': '请在下面贴入您的文本', + 'Paste Text': '粘贴纯文本', + 'Bullet list': '符号列表', + 'Numbered list': '编号列表', + 'Undo': '恢复', + 'Redo': '撤消', + 'Rows:': '行数', + 'Cols:': '列数', + 'Insert a table': '插入表格', + 'Insert a horizontal rule': '插入分隔符', + 'Code': '代码', + 'Width (optional):': '宽度(选填)', + 'Height (optional):': '高度(选填)', + 'Insert an image': '插入图片', + 'E-mail:': 'Email地址', + 'Insert an email': '插入Email地址', + 'URL:': '网址', + 'Insert a link': '插入链接', + 'Unlink': '取消链接', + 'More': '更多', + 'Insert an emoticon': '插入表情符号', + 'Video URL:': '视频地址', + 'Insert': '插入', + 'Insert a YouTube video': '插入YouTube视频', + 'Insert current date': '插入当前日期', + 'Insert current time': '插入当前时间', + 'Print': '打印', + 'View source': '查看代码', + 'Description (optional):': '描述(选填)', + 'Enter the image URL:': '输入图片地址', + 'Enter the e-mail address:': '输入email地址', + 'Enter the displayed text:': '输入显示文字', + 'Enter URL:': '输入网址', + 'Enter the YouTube video URL or ID:': '输入YouTube地址或编号', + 'Insert a Quote': '插入引用', + 'Invalid YouTube video': '无效的YouTube视频', + + dateFormat: 'year-month-day' + }; +})(); diff --git a/public/assets/languages/cs.js b/public/assets/languages/cs.js new file mode 100644 index 0000000..c17e1bf --- /dev/null +++ b/public/assets/languages/cs.js @@ -0,0 +1,71 @@ +/** + * @author Daniel Vítek danielvitek1@gmail.com danvitek.cz + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['cs'] = { + 'Bold': 'Tučné', + 'Italic': 'Kurzíva', + 'Underline': 'Podtržené', + 'Strikethrough': 'Přeškrtnuté', + 'Subscript': 'Dolní index', + 'Superscript': 'Horní index', + 'Align left': 'Zarovnat vlevo', + 'Center': 'Zarovnat na střed', + 'Align right': 'Zarovnat vpravo', + 'Justify': 'Zarovnat do bloku', + 'Font Name': 'Výběr písma', + 'Font Size': 'Velikost písma', + 'Font Color': 'Barva písma', + 'Remove Formatting': 'Vymazat formátování', + 'Cut': 'Vyjmout', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Váš prohlížeč nepodporuje tento příkaz, použijte CTRL+X', + 'Copy': 'Kopírovat', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Váš prohlížeč nepodporuje tento příkaz, použijte CTRL+C', + 'Paste': 'Vložit', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Váš prohlížeč nepodporuje tento příkaz, použijte CTRL+V', + 'Paste your text inside the following box:': 'Vložte Váš text do následujícího pole', + 'Paste Text': 'Vložit text', + 'Bullet list': 'Seznam', + 'Numbered list': 'Číslovaný seznam', + 'Undo': 'Zpět', + 'Redo': 'Vpřed', + 'Rows:': 'Řádků', + 'Cols:': 'Buněk', + 'Insert a table': 'Vložit tabulku', + 'Insert a horizontal rule': 'Vložit vodorovnou čáru', + 'Code': 'Vložit kód', + 'Width (optional):': 'Šířka (volitelné)', + 'Height (optional):': 'Výška (volitelné)', + 'Insert an image': 'Vložit obrázek', + 'E-mail:': 'E-mailová adresa', + 'Insert an email': 'Vložit e-mail', + 'URL:': 'Adresa', + 'Insert a link': 'Vložit odkaz', + 'Unlink': 'Zrušit odkaz', + 'More': 'Více', + 'Insert an emoticon': 'Vložit smajlíka', + 'Video URL:': 'Adresa videa', + 'Insert': 'Vložit', + 'Insert a YouTube video': 'Vložte video z YouTube', + 'Insert current date': 'Vložte aktuální datum', + 'Insert current time': 'Vložte aktuální čas', + 'Print': 'Vytisknout', + 'View source': 'Zobrazit zdroj', + 'Description (optional):': 'Popis (volitelné)', + 'Enter the image URL:': 'Vložte adresu obrázku', + 'Enter the e-mail address:': 'Vložte e-mailovou adresu', + 'Enter the displayed text:': 'Vložte zobrazovaný text', + 'Enter URL:': 'Vložte adresu', + 'Enter the YouTube video URL or ID:': 'Vložte adresu YouTube videa nebo ID videa', + 'Insert a Quote': 'Vložit citát', + 'Invalid YouTube video': 'Neplatné YouTube video', + 'Add indent': 'Posunout na další úroveň', + 'Remove one indent': 'Posunout na předchozí úroveň', + 'Maximize': 'Zobrazit přes celou obrazovku', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/de.js b/public/assets/languages/de.js new file mode 100644 index 0000000..58240c9 --- /dev/null +++ b/public/assets/languages/de.js @@ -0,0 +1,59 @@ +(function () { + 'use strict'; + + sceditor.locale['de'] = { + 'Bold': 'Fett', + 'Italic': 'Kursiv', + 'Underline': 'Unterstrichen', + 'Strikethrough': 'Durchgestrichen', + 'Subscript': 'Tiefgestellt', + 'Superscript': 'Hochgestellt', + 'Align left': 'Linksbündig ausrichten', + 'Center': 'Zentrieren', + 'Align right': 'Rechtsbündig ausrichten', + 'Justify': 'Blocksatz', + 'Font Name': 'Schriftname', + 'Font Size': 'Schriftgröße', + 'Font Color': 'Schriftfarbe', + 'Remove Formatting': 'Formatierung entfernen', + 'Cut': 'Ausschneiden', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ihr Browser erlaubt das Ausschneiden von Text nicht, bitte Nutzen Sie das Tastenkürzel Strg / Cmd-X', + 'Copy': 'Kopieren', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ihr Browser erlaubt das Kopieren von Text nicht, bitte Nutzen Sie das Tastenkürzel Strg / Cmd-C', + 'Paste': 'Einfügen', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ihr Browser erlaubt das Einfügen von Text nicht, bitte Nutzen Sie das Tastenkürzel Strg / Cmd-V', + 'Paste your text inside the following box:': 'Fügen Sie Ihren Text in die folgende Box ein', + 'Paste Text': 'Text einfügen', + 'Bullet list': 'Aufzählungsliste', + 'Numbered list': 'Nummerierte Liste', + 'Undo': 'Rückgängig machen', + 'Redo': 'Wiederherstellen', + 'Rows:': 'Zeilen', + 'Cols:': 'Spalten', + 'Insert a table': 'Tabelle einfügen', + 'Insert a horizontal rule': 'Horizontale Linie einfügen', + 'Code': 'Code', + 'Insert a Quote': 'Zitat einfügen', + 'Width (optional):': 'Breite (Optional)', + 'Height (optional):': 'Höhe (Optional)', + 'Insert an image': 'Ein Bild einfügen', + 'E-mail:': 'E-Mail', + 'Insert an email': 'E-Mail einfügen', + 'URL:': 'URL', + 'Insert a link': 'Link einfügen', + 'Unlink': 'Link entfernen', + 'More': 'Mehr', + 'Left-to-Right': 'Links nach rechts', + 'Right-to-Left': 'Rechts nach links', + 'Insert an emoticon': 'Emoticon einfügen', + 'Video URL:': 'Video URL', + 'Insert': 'Einfügen', + 'Insert a YouTube video': 'YouTube Video einfügen', + 'Insert current date': 'Aktuelles Datum einfügen', + 'Insert current time': 'Aktuelle Uhrzeit einfügen', + 'Print': 'Drucken', + 'Maximize': 'Maximieren', + 'View source': 'Quelltext ansehen', + dateFormat: 'day.month.year' + }; +})(); diff --git a/public/assets/languages/el.js b/public/assets/languages/el.js new file mode 100644 index 0000000..05d33ec --- /dev/null +++ b/public/assets/languages/el.js @@ -0,0 +1,68 @@ +/** + * @author Nikos Aggelis nikosaggelis@hotmail.gr + */ +(function () { + 'use strict'; + + sceditor.locale['el'] = { + 'Bold': 'Έντονα', + 'Italic': 'Πλάγια', + 'Underline': 'Υπογραμμισμένα', + 'Strikethrough': 'Διαγραμμισμένα', + 'Subscript': 'Δείκτης', + 'Superscript': 'Εκθέτης', + 'Align left': 'Αριστερή στοίχιση', + 'Center': 'Κεντραρισμένα', + 'Align right': 'Δεξιά στοίχιση', + 'Justify': 'Πλήρης στοίχιση', + 'Font Name': 'Γραμματοσειρά', + 'Font Size': 'Μέγεθος', + 'Font Color': 'Χρώμα', + 'Remove Formatting': 'Αφαίρεση μορφοποίησης', + 'Cut': 'Αποκοπή', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ο περιηγητής σας δεν επιτρέπει την εντολή αποκοπής. Παρακαλούμε χρησιμοποιήστε τη συντόμευση πληκτρολογίου Ctrl/Cmd-X', + 'Copy': 'Αντιγραφή', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ο περιηγητής σας δεν επιτρέπει την εντολή αντιγραφής. Παρακαλούμε χρησιμοποιήστε τη συντόμευση πληκτρολογίου Ctrl/Cmd-C', + 'Paste': 'Επικόλληση', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ο περιηγητής σας δεν επιτρέπει την εντολή επικόλλησης. Παρακαλούμε χρησιμοποιήστε τη συντόμευση πληκτρολογίου Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Επικολλήστε το κείμενό σας μέσα στο ακόλουθο πλαίσιο:', + 'Paste Text': 'Επικόλληση κειμένου', + 'Bullet list': 'Λίστα με κουκίδες', + 'Numbered list': 'Λίστα με αρίθμηση', + 'Undo': 'Αναίρεση', + 'Redo': 'Επανάληψη', + 'Rows:': 'Γραμμές', + 'Cols:': 'Στήλες', + 'Insert a table': 'Εισαγωγή πίνακα', + 'Insert a horizontal rule': 'Εισαγωγή οριζόντιας γραμμής', + 'Code': 'Κώδικας', + 'Width (optional):': 'Πλάτος (Προαιρετικό)', + 'Height (optional):': 'Ύψος (Προαιρετικό)', + 'Insert an image': 'Εισαγωγή εικόνας', + 'E-mail:': 'Ηλεκτρονικό ταχυδρομείο', + 'Insert an email': 'Εισαγωγή email', + 'URL:': 'Ηλεκτρονική διεύθυνση', + 'Insert a link': 'Εισαγωγή συνδέσμου', + 'Unlink': 'Κατάργηση σύνδεσης', + 'More': 'Περισσότερα', + 'Insert an emoticon': 'Εισαγωγή φατσούλας', + 'Video URL:': 'Διεύθυνση βίντεο', + 'Insert': 'Εισαγωγή', + 'Insert a YouTube video': 'Εισαγωγή βίντεο YouTube', + 'Insert current date': 'Εισαγωγή τρέχουσας ημερομηνίας', + 'Insert current time': 'Εισαγωγή τρέχουσας ώρας', + 'Print': 'Εκτύπωση', + 'Maximize': 'Μεγιστοποίηση', + 'View source': 'Προβολή πηγαίου κώδικα', + 'Description (optional):': 'Περιγραφή (προαιρετικό)', + 'Enter the image URL:': 'Εισάγετε τη διεύθυνση εικόνας', + 'Enter the e-mail address:': 'Εισάγετε τη διεύθυνση e-mail', + 'Enter the displayed text:': 'Εισάγετε το εμφανιζόμενο κείμενο', + 'Enter URL:': 'Εισάγετε διεύθυνση', + 'Enter the YouTube video URL or ID:': 'Εισάγετε τη διεύθυνση του βίντεο YouTube ή το ID', + 'Insert a Quote': 'Εισαγωγή παράθεσης', + 'Invalid YouTube video': 'Μη έγκυρο βίντεο YouTube', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/en-US.js b/public/assets/languages/en-US.js new file mode 100644 index 0000000..cef1211 --- /dev/null +++ b/public/assets/languages/en-US.js @@ -0,0 +1,7 @@ +(function () { + 'use strict'; + + sceditor.locale['en-US'] = { + dateFormat: 'month/day/year' + }; +})(); diff --git a/public/assets/languages/en.js b/public/assets/languages/en.js new file mode 100644 index 0000000..dcb48d7 --- /dev/null +++ b/public/assets/languages/en.js @@ -0,0 +1,12 @@ +(function () { + 'use strict'; + + sceditor.locale['en-GB'] = { + 'Font Color': 'Font Colour', + 'Center': 'Centre', + dateFormat: 'day/month/year' + }; + + // set this as the default English locale + sceditor.locale['en'] = sceditor.locale['en-GB']; +})(); diff --git a/public/assets/languages/es.js b/public/assets/languages/es.js new file mode 100644 index 0000000..aece0ab --- /dev/null +++ b/public/assets/languages/es.js @@ -0,0 +1,68 @@ +/** + * @author + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['es'] = { + 'Bold': 'Negrita', + 'Italic': 'Cursiva', + 'Underline': 'Subrayar', + 'Strikethrough': 'Tachar', + 'Subscript': 'Sub\u00edndice', + 'Superscript': 'Super\u00edndice', + 'Align left': 'Alinear a la Izquierda', + 'Center': 'Centrar', + 'Align right': 'Alinear a la Derecha', + 'Justify': 'Justificar', + 'Font Name': 'Tipo de Letra', + 'Font Size': 'Tama\u00f1o de Letra', + 'Font Color': 'Color de Fuente', + 'Remove Formatting': 'Quitar Formatos', + 'Cut': 'Cortar', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Su navegador no acepta el comando cortar. Por favor, use la combinaci\u00f3n Ctrl/Cmd-X', + 'Copy': 'Copiar', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Su navegador no acepta el comando copiar. Por favor, use la combinaci\u00f3n Ctrl/Cmd-C', + 'Paste': 'Pegar', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Su navegador no acepta el comando pegar. Por favor, use la combinaci\u00f3n Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Pega el texto dentro del siguiente recuadro', + 'Paste Text': 'Pegar Texto', + 'Bullet list': 'Lista de Vi\u00f1etas', + 'Numbered list': 'Lista Numerada', + 'Undo': 'Deshacer', + 'Redo': 'Rehacer', + 'Rows:': 'Filas', + 'Cols:': 'Columnas', + 'Insert a table': 'Insertar una Tabla', + 'Insert a horizontal rule': 'Insertar una Regla Horizontal', + 'Code': 'C\u00f3digo', + 'Width (optional):': 'Ancho (Opcional)', + 'Height (optional):': 'Altura (Opcional)', + 'Insert an image': 'Insertar una Imagen', + 'E-mail:': 'E-mail', + 'Insert an email': 'Insertar un Email', + 'URL:': 'URL', + 'Insert a link': 'Insertar un V\u00ednculo', + 'Unlink': 'Quitar V\u00ednculo', + 'More': 'M\u00e1s', + 'Insert an emoticon': 'Insertar un emoticon', + 'Video URL:': 'URL del V\u00eddeo', + 'Insert': 'Insertar', + 'Insert a YouTube video': 'Insertar un v\u00eddeo de YouTube', + 'Insert current date': 'Insertar fecha actual', + 'Insert current time': 'Insertar hora actual', + 'Print': 'Imprimir', + 'View source': 'Ver C\u00f3digo', + 'Description (optional):': 'Descripci\u00f3n (Opcional):', + 'Enter the image URL:': 'Ingresar la URL de la imagen:', + 'Enter the e-mail address:': 'Ingresar el correo electr\u00f3nico:', + 'Enter the displayed text:': 'Ingresar el texto mostrado:', + 'Enter URL:': 'Ingresar URL:', + 'Enter the YouTube video URL or ID:': 'Ingresar URL o ID de YouTube', + 'Insert a Quote': 'Insertar Cita', + 'Invalid YouTube video': 'Video de YouTube Inv\u00e1lido', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/et.js b/public/assets/languages/et.js new file mode 100644 index 0000000..3c789b2 --- /dev/null +++ b/public/assets/languages/et.js @@ -0,0 +1,57 @@ +(function () { + 'use strict'; + + sceditor.locale['et'] = { + 'Bold': 'Rasvane', + 'Italic': 'Kaldkiri', + 'Underline': 'Allajoonitud', + 'Strikethrough': 'Läbijoonitud', + 'Subscript': 'Allindeks', + 'Superscript': 'Ülaindeks', + 'Align left': 'Joonad vasakule', + 'Center': 'Joonda keskele', + 'Align right': 'Joonda paremale', + 'Justify': 'Joondus mõlemale poole', + 'Font Name': 'Fondi nimi', + 'Font Size': 'Fondi suurus', + 'Font Color': 'Fondi värv', + 'Remove Formatting': 'Eemalda vormindus', + 'Cut': 'Lõika', + 'Sinu veebilehitseja ei luba lõikamise käsu kasutamist. Palun kasuta kiirklahvi Ctrl/Cmd-X': '... Ctrl / Cmd-X', + 'Copy': 'Kopeeri', + 'Sinu veebilehitseja ei luba kopeerimise käsu kasutamist. Palun kasuta kiirklahvi Ctrl/Cmd-C': '... Ctrl / Cmd-C', + 'Paste': 'Aseta', + 'Sinu veebilehitseja ei luba asetamise käsu kasutamist. Palun kasuta kiirklahvi Ctrl/Cmd-V': '... Ctrl / Cmd-V', + 'Paste your text inside the following box:': 'Aseta oma tekst järgneva tekstikasti sisse', + 'Paste Text': 'Aseta tekstina', + 'Bullet list': 'Nimekiri', + 'Numbered list': 'Nummerdatud nimekiri', + 'Undo': 'Samm tagasi', + 'Redo': 'Samm edasi', + 'Rows:': 'Read', + 'Cols:': 'Veerud', + 'Insert a table': 'Sisesta tabel', + 'Insert a horizontal rule': 'Sisesta horisontaalne joon', + 'Code': 'Kood', + 'Insert a Quote': 'Sisesta tsitaat', + 'Width (optional):': 'Laius (Valikuline)', + 'Height (optional):': 'Kõrgus (Valikuline)', + 'Insert an image': 'Sisesta pilt', + 'E-mail:': 'E-post', + 'Insert an email': 'Sisesta e-posti aadress', + 'URL:': 'Link', + 'Insert a link': 'Sisesta link', + 'Unlink': 'Eemalda link', + 'More': 'Veel', + 'Insert an emoticon': 'Sisesta emotikon', + 'Video URL:': 'Video link', + 'Insert': 'Sisesta', + 'Insert a YouTube video': 'Sisesta YouTube video', + 'Insert current date': 'Sisesta praegune kuupäev', + 'Insert current time': 'Sisesta praegune kellaaeg', + 'Print': 'Prindi', + 'View source': 'Vaata lähtekoodi', + + dateFormat: 'day.month.year' + }; +})(); diff --git a/public/assets/languages/fa.js b/public/assets/languages/fa.js new file mode 100644 index 0000000..0c2c02d --- /dev/null +++ b/public/assets/languages/fa.js @@ -0,0 +1,69 @@ +// add locale:'fa', to your config options. +// Translated By Ebad Ghafoory [info@ghafoory.com] +// 2013/05/01 +(function () { + 'use strict'; + + sceditor.locale['fa'] = { + 'Bold': 'تیره', + 'Italic': 'مورب', + 'Underline': 'زیرخط', + 'Strikethrough': 'خط خورده', + 'Subscript': 'زیرنویس', + 'Superscript': 'بالانویس', + 'Align left': 'چپ چین', + 'Center': 'وسط چین', + 'Align right': 'راست چین', + 'Justify': 'همخط', + 'Font Name': 'نام قلم', + 'Font Size': 'اندازه\u200cی نوشته', + 'Font Color': 'رنگ نوشته', + 'Remove Formatting': 'پاکسازی فرمت نوشته', + 'Cut': 'برش', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'مرورگر شما اجازه برش توسط نرم\u200cافزار را نمی\u200cدهد. لطفا از دکمه\u200cهای ترکیبی Ctrl / Cmd-X استفاده کنید', + 'Copy': 'کپی', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'مرورگر شما اجازه کپی کردن توسط نرم\u200cافزار را نمی\u200cدهد. لطفا از دکمه\u200cهای ترکیبی Ctrl / Cmd-C استفاده کنید', + 'Paste': 'چسباندن', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'مرورگر شما اجازه چسباندن توسط نرم\u200cافزار را نمی\u200cدهد. لطفا از دکمه\u200cهای ترکیبی Ctrl / Cmd-V استفاده کنید', + 'Paste your text inside the following box:': 'متن خود را در داخل کادر زیر بچسبانید', + 'Paste Text': 'چسباندن متن', + 'Bullet list': 'لیست', + 'Numbered list': 'لیست عددی', + 'Undo': 'حرکت قبل', + 'Redo': 'حرکت بعد', + 'Rows:': 'تعداد ردیف', + 'Cols:': 'تعداد ستون', + 'Insert a table': 'افزودن جدول', + 'Insert a horizontal rule': 'افزودن خط افقی', + 'Code': 'کد', + 'Insert a Quote': 'افزودن نقل قول', + 'Width (optional):': 'پهنا (دلخواه):', + 'Height (optional):': 'ارتفاع (دلخواه):', + 'Insert an image': 'افزودن عکس', + 'E-mail:': 'ایمیل', + 'Insert an email': 'افزودن ایمیل', + 'URL:': 'آدرس اینترنتی', + 'Insert a link': 'افزودن لینک', + 'Unlink': 'حذف لینک', + 'More': 'بیشتر', + 'Insert an emoticon': 'افزودن شکلک', + 'Video URL:': 'آدرس اینترنتی ویدیو', + 'Insert': 'افزودن', + 'Insert a YouTube video': 'افزودن فیلم از یوتوب', + 'Insert current date': 'افزودن تاریخ اکنون', + 'Insert current time': 'افزودن زمان اکنون', + 'Print': 'چاپ', + 'View source': 'مشاهده سورس', + 'Description (optional):': 'توضیحات (دلخواه):', + 'Enter the image URL:': 'آدرس اینترنتی عکس را وارد کنید:', + 'Enter the e-mail address:': 'آدرس ایمیل را وارد کنید:', + 'Enter the displayed text:': 'متن نمایش\u200cدهنده را وارد کنید:', + 'Enter URL:': 'آدرس اینترنتی را وارد کنید:', + 'Enter the YouTube video URL or ID:': 'آدرس اینترنتی فیلم یوتوب یا شناسه ویدیو را وارد کنید:', + 'Invalid YouTube video': 'فیلم یوتوب غیر معتبر است', + 'Right-to-Left': 'راست به چپ', + 'Left-to-Right': 'چپ به راست', + + dateFormat: 'year.month.day' + }; +})(); diff --git a/public/assets/languages/fr.js b/public/assets/languages/fr.js new file mode 100644 index 0000000..9c98a89 --- /dev/null +++ b/public/assets/languages/fr.js @@ -0,0 +1,70 @@ + +// add locale:'fr', to your config options. + +(function () { + 'use strict'; + + sceditor.locale['fr-FR'] = { + 'Bold': 'Gras', + 'Italic': 'Italique', + 'Underline': 'Souligné', + 'Strikethrough': 'Barré', + 'Subscript': 'Indice', + 'Superscript': 'Exposant', + 'Align left': 'Aligner à gauche', + 'Center': 'Centrer', + 'Align right': 'Aligner à droite', + 'Justify': 'Justifier', + 'Font Name': 'Police', + 'Font Size': 'Taille de police', + 'Font Color': 'Couleur de police', + 'Remove Formatting': 'Enlever le formatage', + 'Cut': 'Couper', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Votre navigateur n\'autorise pas la commande \'Couper\'. Merci d\'utiliser le raccourcis clavier Ctrl/Cmd+X', + 'Copy': 'Copier', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Votre navigateur n\'autorise pas la commande \'Copier\'. Merci d\'utiliser le raccourcis clavier Ctrl/Cmd+C', + 'Paste': 'Coller', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Votre navigateur n\'autorise pas la commande \'Coller\'. Merci d\'utiliser le raccourcis clavier Ctrl/Cmd+V', + 'Paste your text inside the following box:': 'Collez votre texte à l\'intérieur de ce bloc', + 'Paste Text': 'Texte collé', + 'Bullet list': 'Liste à puce', + 'Numbered list': 'Liste numérotée', + 'Undo': 'Annuler', + 'Redo': 'Rétablir', + 'Rows:': 'Lignes', + 'Cols:': 'Colonnes', + 'Insert a table': 'Insérer un tableau', + 'Insert a horizontal rule': 'Insérer une ligne horizontale', + 'Code': 'Code', + 'Insert a Quote': 'Insérer une citation', + 'Width (optional):': 'Largeur (Optionnelle)', + 'Height (optional):': 'Hauteur (Optionnelle)', + 'Insert an image': 'Insérer une image', + 'E-mail:': 'Courriel', + 'Insert an email': 'Insérer un courriel', + 'URL:': 'URL', + 'Insert a link': 'Insérer un lien', + 'Unlink': 'Supprimer un lien', + 'More': 'Plus', + 'Insert an emoticon': 'Insérer une émoticône', + 'Video URL:': 'URL Vidéo', + 'Insert': 'Insérer', + 'Insert a YouTube video': 'Insérer une vidéo YouTube', + 'Insert current date': 'Insérer la date actuelle', + 'Insert current time': 'Insérer l\'heure actuelle', + 'Print': 'Imprimer', + 'View source': 'Afficher le texte brut', + 'Description (optional):': 'Description (Optionnelle)', + 'Enter the image URL:': 'Entrez l\'URL de l\'image:', + 'Enter the e-mail address:': 'Entrez le courriel:', + 'Enter the displayed text:': 'Entrez le texte affiché:', + 'Enter URL:': 'Entrez une URL:', + 'Enter the YouTube video URL or ID:': 'Entrez l\'URL ou l\'ID de la vidéo YouTube:', + 'Invalid YouTube video': 'Vidéo YouTube invalide', + 'Right-to-Left': 'De droite à gauche', + 'Left-to-Right': 'De gauche à droite', + + dateFormat: 'day/month/year' + }; + sceditor.locale['fr'] = sceditor.locale['fr-FR']; +})(); diff --git a/public/assets/languages/gl.js b/public/assets/languages/gl.js new file mode 100644 index 0000000..ec6e1a9 --- /dev/null +++ b/public/assets/languages/gl.js @@ -0,0 +1,68 @@ +/** + * @author Fran Sobrino + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['gl'] = { + 'Bold': 'Negrita', + 'Italic': 'Cursiva', + 'Underline': 'Subrayar', + 'Strikethrough': 'Riscar', + 'Subscript': 'Sub\u00edndice', + 'Superscript': 'Super\u00edndice', + 'Align left': 'Alinear á Esquerda', + 'Center': 'Centrar', + 'Align right': 'Alinear á Dereita', + 'Justify': 'Xustificar', + 'Font Name': 'Tipo de Letra', + 'Font Size': 'Tama\u00f1o de Letra', + 'Font Color': 'Cor de Fonte', + 'Remove Formatting': 'Quitar Formatos', + 'Cut': 'Cortar', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-X', + 'Copy': 'Copiar', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-C', + 'Paste': 'Pegar', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'O seu navegador non acepta o comando cortar. Por favor, empregue a combinaci\u00f3n Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Pega o texto dentro do seguinte recadro', + 'Paste Text': 'Pegar Texto', + 'Bullet list': 'Lista de Vi\u00f1etas', + 'Numbered list': 'Lista Numerada', + 'Undo': 'Desfacer', + 'Redo': 'Refacer', + 'Rows:': 'Ringleiras', + 'Cols:': 'Columnas', + 'Insert a table': 'Engadir unha Tabla', + 'Insert a horizontal rule': 'Engadir unha Regla Horizontal', + 'Code': 'C\u00f3digo', + 'Width (optional):': 'Ancho (Opcional)', + 'Height (optional):': 'Altura (Opcional)', + 'Insert an image': 'Engadir unha Imaxen', + 'E-mail:': 'E-mail', + 'Insert an email': 'Engadir un Email', + 'URL:': 'URL', + 'Insert a link': 'Engadir un V\u00ednculo', + 'Unlink': 'Quitar V\u00ednculo', + 'More': 'M\u00e1is', + 'Insert an emoticon': 'Engadir un emoticon', + 'Video URL:': 'URL do V\u00eddeo', + 'Insert': 'Engadir', + 'Insert a YouTube video': 'Engadir un v\u00eddeo de YouTube', + 'Insert current date': 'Engadir data actual', + 'Insert current time': 'Engadir hora actual', + 'Print': 'Imprimir', + 'View source': 'Ver C\u00f3digo', + 'Description (optional):': 'Descripci\u00f3n (Opcional):', + 'Enter the image URL:': 'Ingresar a URL da imaxen:', + 'Enter the e-mail address:': 'Ingresar o correo electr\u00f3nico:', + 'Enter the displayed text:': 'Ingresar o texto mostrado:', + 'Enter URL:': 'Ingresar URL:', + 'Enter the YouTube video URL or ID:': 'Ingresar URL ou ID de YouTube', + 'Insert a Quote': 'Engadir Cita', + 'Invalid YouTube video': 'V\u00eddeo de YouTube Inv\u00e1lido', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/hu.js b/public/assets/languages/hu.js new file mode 100644 index 0000000..0fd64a3 --- /dev/null +++ b/public/assets/languages/hu.js @@ -0,0 +1,69 @@ +/** + * @author Ángyán László + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + * @date 2013-08-11 + */ +(function () { + 'use strict'; + + sceditor.locale['hu'] = { + 'Bold': 'Félkövér', + 'Italic': 'Dőlt', + 'Underline': 'Aláhúzva', + 'Strikethrough': 'Áthúzva', + 'Subscript': 'Alsó index', + 'Superscript': 'Felső index', + 'Align left': 'Balra zárt', + 'Center': 'Középre zárt', + 'Align right': 'Jobbra zárt', + 'Justify': 'Sorkizárt', + 'Font Name': 'Betűtípus', + 'Font Size': 'Betű méret', + 'Font Color': 'Betű szín', + 'Remove Formatting': 'Formázás törlése', + 'Cut': 'Kivágás', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'A böngésző biztonsági beállításai nem engedik a kivágást. Használd a Ctrl/Cmd+X billetyűket.', + 'Copy': 'Másolás', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'A böngésző biztonsági beállításai nem engedik a másolást. Használd a Ctrl/Cmd+C billetyűket.', + 'Paste': 'Beillesztés', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'A böngésző biztonsági beállításai nem engedik a beillesztést. Használd a Ctrl/Cmd+V billetyűket.', + 'Paste your text inside the following box:': 'Illeszd be a szöveget a dobozba:', + 'Paste Text': 'Szöveg beszúrása', + 'Bullet list': 'Felsorolás', + 'Numbered list': 'Sorszámozott felsorolás', + 'Undo': 'Vissza', + 'Redo': 'Mégis', + 'Rows:': 'Sorok', + 'Cols:': 'Oszlopok', + 'Insert a table': 'Táblázat beszúrása', + 'Insert a horizontal rule': 'Vízszintes vonal beszúrása', + 'Code': 'Kód', + 'Width (optional):': 'Szélesség (nem kötelező):', + 'Height (optional):': 'Magasság (nem kötelező):', + 'Insert an image': 'Illessz be egy képet', + 'E-mail:': 'Email:', + 'Insert an email': 'Illessz be egy email címet.', + 'URL:': 'Honlap', + 'Insert a link': 'Hivatkozás létrehozása', + 'Unlink': 'Hivatkozás megszüntetése', + 'More': 'Több', + 'Insert an emoticon': 'Smiley beszúrása', + 'Video URL:': 'Video link:', + 'Insert': 'Beszúrás', + 'Insert a YouTube video': 'Youtube video beszúrása', + 'Insert current date': 'Szúrd be az aktuális dátumot', + 'Insert current time': 'Szúrd be a jelenlegi időt', + 'Print': 'Nyomtatás', + 'View source': 'Forrás', + 'Description (optional):': 'Hivatkozás szövege (nem kötelező)', + 'Enter the image URL:': 'Kép URL beillesztése:', + 'Enter the e-mail address:': 'Írd be az email címet:', + 'Enter the displayed text:': 'Írd be a megjelenítendő szöveget:', + 'Enter URL:': 'Írd be a linket:', + 'Enter the YouTube video URL or ID:': 'Írd be a Youtube video URL-jét vagy azonosítóját', + 'Insert a Quote': 'Idézet beszúrása', + 'Invalid YouTube video': 'Érvénytelen Youtube link', + + dateFormat: 'year.month.day.' + }; +})(); diff --git a/public/assets/languages/id.js b/public/assets/languages/id.js new file mode 100644 index 0000000..3ee7783 --- /dev/null +++ b/public/assets/languages/id.js @@ -0,0 +1,68 @@ +/** + * @author Sandy Irawan (sndbkct@gmail.com) + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['id'] = { + 'Bold': 'Tebal', + 'Italic': 'Miring', + 'Underline': 'Garis Bawah', + 'Strikethrough': 'Coret', + 'Subscript': 'Tulisan dibawah garis', + 'Superscript': 'Tulisan diatas garis', + 'Align left': 'Rata Kiri', + 'Center': 'Rata Tengah', + 'Align right': 'Rata Kanan', + 'Justify': 'Rata Kanan-Kiri', + 'Font Name': 'Nama Fon', + 'Font Size': 'Ukuran Fon', + 'Font Color': 'Warna Fon', + 'Remove Formatting': 'Hapus Format', + 'Cut': 'Potong', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Browser Anda tidak memungkinkan perintah cut. Silakan gunakan shortcut keyboard Ctrl / Cmd-X ', + 'Copy': 'Salin', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Browser Anda tidak memungkinkan perintah copy. Silakan gunakan shortcut keyboard Ctrl / Cmd-C ', + 'Paste': 'Rekatkan', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Browser Anda tidak memungkinkan perintah paste. Silakan gunakan shortcut keyboard Ctrl / Cmd-V ', + 'Paste your text inside the following box:': 'Rekatkan teks Anda dalam kotak berikut:', + 'Paste Text': 'Rekatkan Teks', + 'Bullet list': 'Daftar Bullet', + 'Numbered list': 'Daftar Nomor', + 'Undo': 'Kembalikan', + 'Redo': 'Ulangi', + 'Rows:': 'Baris', + 'Cols:': 'Kolom', + 'Insert a table': 'Sisipkan sebuah tabel', + 'Insert a horizontal rule': 'Sisipkan aturan horisontal', + 'Code': 'Kode', + 'Width (optional):': 'Lebar (opsional)', + 'Height (optional):': 'Tinggi (opsional)', + 'Insert an image': 'Sisipkan Gambar', + 'E-mail:': 'Surel', + 'Insert an email': 'Sisipkan surel', + 'URL:': 'URL', + 'Insert a link': 'Sisipkan link', + 'Unlink': 'Buang Link', + 'More': 'Lainnya', + 'Insert an emoticon': 'Sisipkan emotikon', + 'Video URL:': 'URL Video', + 'Insert': 'Sisipkan', + 'Insert a YouTube video': 'Sisipkan video Youtube', + 'Insert current date': 'Sisipkan tanggal sekarang', + 'Insert current time': 'Sisipkan waktu sekarang', + 'Print': 'Print', + 'View source': 'Lihat sumber', + 'Description (optional):': 'Deskripsi (opsional)', + 'Enter the image URL:': 'Masukkan URL gambar', + 'Enter the e-mail address:': 'Masukkan alamat surel', + 'Enter the displayed text:': 'Masukkan teks yang ditampilkan', + 'Enter URL:': 'Masukkan URL', + 'Enter the YouTube video URL or ID:': 'Masukkan URL video YouTube atau ID', + 'Insert a Quote': 'Sisipkan kutipan', + 'Invalid YouTube video': 'Video YouTube yang tidak valid', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/it.js b/public/assets/languages/it.js new file mode 100644 index 0000000..b37fcc4 --- /dev/null +++ b/public/assets/languages/it.js @@ -0,0 +1,72 @@ +/** + * @author + * @author Gianluca Guazzo + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['it-IT'] = { + 'Bold': 'Grassetto', + 'Italic': 'Corsivo', + 'Underline': 'Sottolineato', + 'Strikethrough': 'Barrato', + 'Subscript': 'Pedice', + 'Superscript': 'Apice', + 'Align left': 'Allinea a sinistra', + 'Center': 'Centrato', + 'Align right': 'Allinea a destra', + 'Justify': 'Giustificato', + 'Font Name': 'Nome carattere', + 'Font Size': 'Dimensione carattere', + 'Font Color': 'Colore carattere', + 'Remove Formatting': 'Rimuovi formattazione', + 'Cut': 'Taglia', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Il tuo browser non permette il comando Taglia. Usa per favore la scorciatoia da tastiera Ctrl/Cmd-X', + 'Copy': 'Copia', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Il tuo browser non permette il comando Copia. Usa per favore la scorciatoia da tastiera Ctrl/Cmd-C', + 'Paste': 'Incolla', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Il tuo browser non permette il comando Incolla. Usa per favore la scorciatoia da tastiera Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Incolla il tuo testo dentro il seguente riquadro', + 'Paste Text': 'Incolla Testo', + 'Bullet list': 'Elenco puntato', + 'Numbered list': 'Elenco numerato', + 'Undo': 'Annulla', + 'Redo': 'Ripeti', + 'Rows:': 'Righe:', + 'Cols:': 'Colonne:', + 'Insert a table': 'Inserisci una tabella', + 'Insert a horizontal rule': 'Inserisci riga orizzontale', + 'Code': 'Codice', + 'Width (optional):': 'Larghezza(opzionale):', + 'Height (optional):': 'Altezza(opzionale):', + 'Insert an image': 'Inserisci un\'immagine', + 'E-mail:': 'E-mail:', + 'Insert an email': 'Inserisci una email', + 'URL:': 'URL:', + 'Insert a link': 'Inserisci collegamento(link):', + 'Unlink': 'Togli collegamento(link):', + 'More': 'Di più', + 'Insert an emoticon': 'Inserisci una emoticon', + 'Video URL:': 'URL del video', + 'Insert': 'Inserisci', + 'Insert a YouTube video': 'Inserisci un video YouTube', + 'Insert current date': 'Inserisci data corrente', + 'Insert current time': 'Inserisci ora corrente', + 'Print': 'Stampa', + 'View source': 'Vedi codice sorgente', + 'Description (optional):': 'Descrizione (opzionale):', + 'Enter the image URL:': 'Inserisci URL dell\'immagine', + 'Enter the e-mail address:': 'Inserisci indirizzo email', + 'Enter the displayed text:': 'Inserisci testo visualizzato', + 'Enter URL:': 'Inserisci URL', + 'Enter the YouTube video URL or ID:': 'Inserisci URL o ID video di YouTube', + 'Insert a Quote': 'Inserisci una citazione', + 'Invalid YouTube video': 'Video YouTube invalido', + + dateFormat: 'day-month-year' + }; + + // Set as the default Italian locale + sceditor.locale['it'] = sceditor.locale['it-IT']; +})(); diff --git a/public/assets/languages/ja.js b/public/assets/languages/ja.js new file mode 100644 index 0000000..c036d6e --- /dev/null +++ b/public/assets/languages/ja.js @@ -0,0 +1,71 @@ +/** + * @author + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['ja'] = { + 'Bold': '太字', + 'Italic': '斜字', + 'Underline': '下線', + 'Strikethrough': '取り消し線', + 'Subscript': '下付き文字', + 'Superscript': '上付き文字', + 'Align left': '左揃え', + 'Center': '中央揃え', + 'Align right': '右揃え', + 'Justify': '均等揃え', + 'Font Name': 'フォント名', + 'Font Size': 'フォントサイズ', + 'Font Color': 'フォントの色', + 'Remove Formatting': '書式解除', + 'Cut': '切り取り', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'お使いのブラウザではカットコマンドを許可されていません。 キーボードショートカットの Ctrl/Cmd-X をお使いください。', + 'Copy': 'コピー', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'お使いのブラウザではコピーコマンドを許可されていません。 キーボードショートカットの Ctrl/Cmd-C をお使いください。', + 'Paste': '貼り付け', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'お使いのブラウザでは貼り付けコマンドを許可されていません。 キーボードショートカットの Ctrl/Cmd-V をお使いください。', + 'Paste your text inside the following box:': '以下にテキストを貼り付けてください。', + 'Paste Text': 'テキストを貼り付け', + 'Bullet list': '箇条書き', + 'Numbered list': '段落番号', + 'Undo': '元に戻す', + 'Redo': 'やり直す', + 'Rows:': '行数', + 'Cols:': '列数', + 'Insert a table': '表を挿入', + 'Insert a horizontal rule': '水平線を挿入', + 'Code': 'コード', + 'Width (optional):': '幅 (オプション)', + 'Height (optional):': '高さ (オプション)', + 'Insert an image': '画像を挿入', + 'E-mail:': 'メールアドレス', + 'Insert an email': 'メールアドレスを挿入', + //'URL:': ', + 'Insert a link': 'リンクを挿入', + 'Unlink': 'リンクを解除', + //'More': ', + 'Insert an emoticon': '顔文字を挿入', + 'Video URL:': '動画URL', + 'Insert': '挿入', + 'Insert a YouTube video': 'Youtubeを挿入', + 'Insert current date': '現在の日付を挿入', + 'Insert current time': '現在の時間を挿入', + 'Print': '印刷', + 'View source': 'ソースを表示', + 'Description (optional):': '説明 (オプション)', + 'Enter the image URL:': '画像URLを入力してください。', + 'Enter the e-mail address:': 'メールアドレスを入力してください。', + 'Enter the displayed text:': '表示テキストを入力してください。', + 'Enter URL:': 'URLを入力してください。', + 'Enter the YouTube video URL or ID:': 'Youtubeの動画URLまたはIDを入力してください。', + 'Insert a Quote': '引用を挿入', + 'Invalid YouTube video': '不正なYoutube動画', + 'Left-to-Right': '左から右へ', + 'Right-to-Left': '右から左へ', + 'Maximize': '最大化', + + dateFormat: 'year-month-day' + }; +})(); diff --git a/public/assets/languages/lt.js b/public/assets/languages/lt.js new file mode 100644 index 0000000..708e83f --- /dev/null +++ b/public/assets/languages/lt.js @@ -0,0 +1,68 @@ +/** + * @author Team from www.klaustukai.lt + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['lt'] = { + 'Bold': 'Paryškintas', + 'Italic': 'Pasvirasis', + 'Underline': 'Pabraukti', + 'Strikethrough': 'Perbraukti', + 'Subscript': 'Parašyti sumažintas raides po žodžio', + 'Superscript': 'Parašyti sumažintas raides virš žodžio', + 'Align left': 'Kairysis lygiavimas', + 'Center': 'Centrinis lygiavimas', + 'Align right': 'Dešinysis lygiavimas', + 'Justify': 'Išlygintas tekstas', + 'Font Name': 'Šrifto pavadinimas', + 'Font Size': 'Šrifto dydis', + 'Font Color': 'Šrifto spalva', + 'Remove Formatting': 'Panaikinti teksto formatavimą', + 'Cut': 'Iškirpti', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Jūsų paieškos sistema neleidžia atlikti šios funkcijos. Norėdami iškirpti spauskite Ctrl/Cmd-x', + 'Copy': 'Kopijuoti', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Jūsų paieškos sistema neleidžia atlikti šios komandos. Norėdami nukopijuoti spauskite Ctrl/Cmd - C', + 'Paste': 'Įklijuoti', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Jūsų paieškos sistema neleidžia atlikti šios komandos. Norėdami įklijuoti spauskite Ctrl/Cmd - V', + 'Paste your text inside the following box:': 'Įklijuokite tekstą nurodytoje vietoje', + 'Paste Text': 'Įklijuoti tekstą', + 'Bullet list': 'Sugrupuotas sąrašas', + 'Numbered list': 'Sunumeruotas sąrašas', + 'Undo': 'panaikinti', + 'Redo': 'atitaisyti', + 'Rows:': 'Eilutės', + 'Cols:': 'Stulpeliai', + 'Insert a table': 'Įterpti lentelę', + 'Insert a horizontal rule': 'Įterpti horizontalią liniją', + 'Code': 'Šalies kodas', + 'Width (optional):': 'plotis (laisvai pasirenkamas)', + 'Height (optional):': 'aukštis (laisvai pasirenkamas)', + 'Insert an image': 'Įterpti nuotrauką', + 'E-mail:': 'Elektroninis paštas', + 'Insert an email': 'Įterpti elktroninio pašto nuorodą', + 'URL:': 'Internetinės svetainės adresas:', + 'Insert a link': 'Įterpti nuorodą', + 'Unlink': 'Atjungti', + 'More': 'Daugiau', + 'Insert an emoticon': 'Įterpti šypsenėlę', + 'Video URL:': 'Vaizdo klipo nuoroda', + 'Insert': 'Įterpti', + 'Insert a YouTube video': 'Įterpti Youtube vaizdo klipą', + 'Insert current date': 'Įterpti esamą datą (diena-mėnuo-metai)', + 'Insert current time': 'Įterpti esamą laiką', + 'Print': 'Atspausdinti', + 'View source': 'Peržiūrėti šaltinį', + 'Description (optional):': 'Aprašymas (laisvai pasirenkamas)', + 'Enter the image URL:': 'Įterpti nuotraukos adresą', + 'Enter the e-mail address:': 'Įterpti elektroninio pašto adresą', + 'Enter the displayed text:': 'Įvesti pavaizduotą tekstą', + 'Enter URL:': 'Įvesti internetinį adresą', + 'Enter the YouTube video URL or ID:': 'Įrašykite Youtube vaizdo klipo nuorodą ar ID', + 'Insert a Quote': 'Įterpti citatą', + 'Invalid YouTube video': 'YouTube vaizdo įrašas neveikia', + + dateFormat: 'year-month-day' + }; +})(); diff --git a/public/assets/languages/nb.js b/public/assets/languages/nb.js new file mode 100644 index 0000000..f58fda7 --- /dev/null +++ b/public/assets/languages/nb.js @@ -0,0 +1,70 @@ +/** + * @author Katrine + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['nb-NO'] = { + 'Bold': 'Fet', + 'Italic': 'Kursiv', + 'Underline': 'Understrek', + 'Strikethrough': 'Gjennomstrek', + 'Subscript': 'Senket', + 'Superscript': 'Hevet', + 'Align left': 'Sidestill til venstre', + 'Center': 'Midstill', + 'Align right': 'Sidestill til høyre', + 'Justify': 'Normalt oppstilt', + 'Font Name': 'Skriftype', + 'Font Size': 'Skriftstørrelse', + 'Font Color': 'skriftfarge', + 'Remove Formatting': 'Fjern formatering', + 'Cut': 'Klipp', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Nettleseren din kan ikke utføre klippe kommandoen. Vennligst bruk hurtigtasten Ctrl / Cmd-X', + 'Copy': 'Kopier', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Nettleseren din kan ikke utføre kopier kommandoen. Vennligst bruk hurtigtasten Ctrl / Cmd-C', + 'Paste': 'Lim', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Nettleseren din kan ikke utføre lime kommandoen. Vennligst bruk hurtigtasten Ctrl / Cmd-V', + 'Paste your text inside the following box:': 'Lim inn teksten i den følgende boksen:', + 'Paste Text': 'Lim inn tekst', + 'Bullet list': 'Bullet liste', + 'Numbered list': 'Nummerert liste', + 'Undo': 'Angre', + 'Redo': 'Gjør på nytt', + 'Rows:': 'Rader', + 'Cols:': 'Kolonner', + 'Insert a table': 'Sett inn en tabell', + 'Insert a horizontal rule': 'Sett en horisontal regel', + 'Code': 'Kode', + 'Width (optional):': 'Bredde (valgfritt):', + 'Height (optional):': 'Høyde (valgfritt):', + 'Insert an image': 'Sett inn et bilde', + 'E-mail:': 'E-post', + 'Insert an email': 'Sett inn en e-post', + 'URL:': 'URL:', + 'Insert a link': 'Sett inn en lenke', + 'Unlink': 'Oppheve tilknytningen', + 'More': 'Mer', + 'Insert an emoticon': 'Sett inn et uttrykksikon', + 'Video URL:': 'Video URL', + 'Insert': 'Sett inn', + 'Insert a YouTube video': 'Sett inn en YouTube-video', + 'Insert current date': 'Sett inn gjeldende dato', + 'Insert current time': 'Sett inn gjeldende klokkeslett', + 'Print': 'Skriv ut', + 'View source': 'Vis kildekode', + 'Description (optional):': 'Beskrivelse (valgfritt):', + 'Enter the image URL:': 'Skriv inn bildet\'s URL:', + 'Enter the e-mail address:': 'Skriv inn e-postadresse:', + 'Enter the displayed text:': 'Skriv inn teksten som vises:', + 'Enter URL:': 'Skriv inn URL adresse:', + 'Enter the YouTube video URL or ID:': 'Angi YouTube video link eller ID:', + 'Insert a Quote': 'Sett inn sitat', + 'Invalid YouTube video': 'Ugyldig Youtube video', + + dateFormat: 'day.month.year' + }; + + sceditor.locale['nb'] = sceditor.locale['nb-NO']; +})(); diff --git a/public/assets/languages/nl.js b/public/assets/languages/nl.js new file mode 100644 index 0000000..64eaa80 --- /dev/null +++ b/public/assets/languages/nl.js @@ -0,0 +1,57 @@ +(function () { + 'use strict'; + + sceditor.locale['nl'] = { + 'Bold': 'Vet', + 'Italic': 'Schuingedrukt', + 'Underline': 'Onderstreept', + 'Strikethrough': 'Doorhalen', + 'Subscript': 'Subscript', + 'Superscript': 'Superscript', + 'Align left': 'Links uitlijnen', + 'Center': 'Centreren', + 'Align right': 'Rechts uitlijnen', + 'Justify': 'Uitvullen', + 'Font Name': 'Fontnaam', + 'Font Size': 'Fontgrootte', + 'Font Color': 'Fontkleur', + 'Remove Formatting': 'Verwijder opmaak', + 'Cut': 'Knippen', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Je browser staat het knippen commando niet toe. Gebruik de toetsenbord sneltoets Ctrl / Cmd-X', + 'Copy': 'Kopiëren', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Je browser staat het kopieer commando niet toe. Gebruik de toetsenbord sneltoets Ctrl / Cmd-C', + 'Paste': 'Plakken', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Je browser staat het plakken commando niet toe. Gebruik de toetsenbord sneltoets Ctrl / Cmd-V', + 'Paste your text inside the following box:': 'Plak je tekst in de volgende locatie:', + 'Paste Text': 'Tekst plakken', + 'Bullet list': 'Opsomming', + 'Numbered list': 'Genummerde lijst', + 'Undo': 'Ongedaan maken', + 'Redo': 'Opnieuw uitvoeren', + 'Rows:': 'Rijen', + 'Cols:': 'Kolommen', + 'Insert a table': 'Tabel', + 'Insert a horizontal rule': 'Horizontale regel', + 'Code': 'Code', + 'Insert a Quote': 'Citeren', + 'Width (optional):': 'Breedte (optioneel):', + 'Height (optional):': 'Hoogte (optioneel):', + 'Insert an image': 'Afbeelding', + 'E-mail:': 'E-mail', + 'Insert an email': 'E-mail', + 'URL:': 'URL:', + 'Insert a link': 'Link', + 'Unlink': 'Link verwijderen', + 'More': 'Meer', + 'Insert an emoticon': 'Emoticon', + 'Video URL:': 'Video URL', + 'Insert': 'Invoegen', + 'Insert a YouTube video': 'YouTube-video', + 'Insert current date': 'Huidige datum', + 'Insert current time': 'Huidige tijd', + 'Print': 'Print', + 'View source': 'Bron bekijken', + + dateFormat: 'day.month.year' + }; +})(); diff --git a/public/assets/languages/pl.js b/public/assets/languages/pl.js new file mode 100644 index 0000000..dabba36 --- /dev/null +++ b/public/assets/languages/pl.js @@ -0,0 +1,68 @@ +/** + * @author + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['pl'] = { + 'Bold': 'Pogrubienie', + 'Italic': 'Kursywa', + 'Underline': 'Podkreślenie', + 'Strikethrough': 'Przekreślenie', + 'Subscript': 'Indeks dolny', + 'Superscript': 'Indeks górny', + 'Align left': 'Do lewej', + 'Center': 'Do środka', + 'Align right': 'Do prawej', + 'Justify': 'Wyjustowanie', + 'Font Name': 'Krój czcionki', + 'Font Size': 'Rozmiar czcionki', + 'Font Color': 'Kolor czcionki', + 'Remove Formatting': 'Usuń formatowanie', + 'Cut': 'Wytnij', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Twoja przeglądarka nie obsługuje opcji wycinania. Użyj skrótu klawiszowego Cmd/Ctrl + X', + 'Copy': 'Skopiuj', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Twoja przeglądarka nie obsługuje opcji kopiowania. Użyj skrótu klawiszowego Cmd/Ctrl + C', + 'Paste': 'Wklej', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Twoja przeglądarka nie obsługuje opcji wklejania. Użyj skrótu klawiszowego Cmd/Ctrl + V', + 'Paste your text inside the following box:': 'Wklej swój tekst do tego pola:', + 'Paste Text': 'Wklej tekst', + 'Bullet list': 'Wypunktowanie', + 'Numbered list': 'Lista numerowana', + 'Undo': 'Cofnij', + 'Redo': 'Powtórz', + 'Rows:': 'Wiersze:', + 'Cols:': 'Kolumny:', + 'Insert a table': 'Wstaw tabelę', + 'Insert a horizontal rule': 'Wstaw linię poziomą', + 'Code': 'Kod', + 'Width (optional):': 'Szerokość (opcjonalnie)', + 'Height (optional):': 'Wysokość (opcjonalnie)', + 'Insert an image': 'Wstaw obrazek', + 'E-mail:': 'E-mail', + 'Insert an email': 'Wstaw e-mail', + 'URL:': 'URL', + 'Insert a link': 'Wstaw odnośnik', + 'Unlink': 'Usuń odnośnik', + 'More': 'Więcej', + 'Insert an emoticon': 'Wstaw emotikonę', + 'Video URL:': 'URL do filmu', + 'Insert': 'Wstaw', + 'Insert a YouTube video': 'Wstaw film YouTube', + 'Insert current date': 'Wstaw aktualną datę', + 'Insert current time': 'Wstaw aktualny czas', + 'Print': 'Drukuj', + 'View source': 'Pokaż źródło', + 'Description (optional):': 'Opis (opcjonalny)', + 'Enter the image URL:': 'Wstaw URL do obrazka', + 'Enter the e-mail address:': 'Wpisz adres e-mail', + 'Enter the displayed text:': 'Wpisz wyświetlony tekst', + 'Enter URL:': 'Wpisz adres URL', + 'Enter the YouTube video URL or ID:': 'Wpisz adres URL lub ID filmu na YouTube', + 'Insert a Quote': 'Wstaw cytat', + 'Invalid YouTube video': 'Nieprawidłowy film YouTube', + + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/pt-BR.js b/public/assets/languages/pt-BR.js new file mode 100644 index 0000000..f21f134 --- /dev/null +++ b/public/assets/languages/pt-BR.js @@ -0,0 +1,67 @@ +/** +* @author martec +* @license [MIT](http://www.opensource.org/licenses/mit-license.php) +*/ +(function () { + 'use strict'; + + sceditor.locale['pt-BR'] = { + 'Bold': 'Negrito', + 'Italic': 'Itálico', + 'Underline': 'Sublinhado', + 'Strikethrough': 'Rasurado', + 'Subscript': 'Subscrito', + 'Superscript': 'Sobrescrito ', + 'Align left': 'Alinhar à esquerda', + 'Center': 'Centralizar', + 'Align right': 'Alinhar à direita', + 'Justify': 'Justificar', + 'Font Name': 'Nome da fonte', + 'Font Size': 'Tamanho da fonte', + 'Font Color': 'Cor da fonte', + 'Remove Formatting': 'Remover a formatação', + 'Cut': 'Recortar', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Seu navegador não permite o comando recortar. Favor use o atalho Ctrl/Cmd-X', + 'Copy': 'Copiar', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Seu navegador não permite o comando copiar. Favor use o atalho Ctrl/Cmd-C', + 'Paste': 'Colar', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Seu navegador não permite o comando colar. Favor use o atalho Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Cole o seu texto dentro da caixa de texto a seguir:', + 'Paste Text': 'Colar o texto', + 'Bullet list': 'Lista com marcadores', + 'Numbered list': 'Lista numérica', + 'Undo': 'Desfazer', + 'Redo': 'Refazer', + 'Rows:': 'Linhas:', + 'Cols:': 'Colunas:', + 'Insert a table': 'Inserir uma tabela', + 'Insert a horizontal rule': 'Inserir uma linha horizontal', + 'Code': 'Código', + 'Width (optional):': 'Largura (opcional):', + 'Height (optional):': 'Altura (opcional):', + 'Insert an image': 'Inserir uma imagem', + 'E-mail:': 'E-mail:', + 'Insert an email': 'Inserir um e-mail', + 'URL:': 'URL:', + 'Insert a link': 'Inserir um hiperlink', + 'Unlink': 'Remover o hiperlink', + 'More': 'Mais', + 'Insert an emoticon': 'Inserir um emoticon', + 'Video URL:': 'Video URL:', + 'Insert': 'Inserir', + 'Insert a YouTube video': 'Inserir YouTube video', + 'Insert current date': 'Inserir a data atual', + 'Insert current time': 'Inserir a hora atual', + 'Print': 'Imprimir', + 'View source': 'Fonte', + 'Description (optional):': 'Descrição (opcional):', + 'Enter the image URL:': 'Informe o endereço URL da imagem:', + 'Enter the e-mail address:': 'Informe o endereço de e-mail:', + 'Enter the displayed text:': 'Digite o texto exibido:', + 'Enter URL:': 'Informe o endereço URL:', + 'Enter the YouTube video URL or ID:': 'Informe o endereço URL ou ID do YouTube:', + 'Insert a Quote': 'Inserir uma citação', + 'Invalid YouTube video': 'Vídeo do YouTube inválido', + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/pt.js b/public/assets/languages/pt.js new file mode 100644 index 0000000..d854515 --- /dev/null +++ b/public/assets/languages/pt.js @@ -0,0 +1,69 @@ +/** +* @author brunoais +* @license [MIT](http://www.opensource.org/licenses/mit-license.php) +*/ +(function () { + 'use strict'; + + sceditor.locale['pt-PT'] = { + 'Bold': 'Negrito', + 'Italic': 'Itálico', + 'Underline': 'Sublinhado', + 'Strikethrough': 'Rasurado', + 'Subscript': 'Subscrito', + 'Superscript': 'Sobrescrito ', + 'Align left': 'Alinhar à esquerda', + 'Center': 'Centrar', + 'Align right': 'Alinhar à direita', + 'Justify': 'Justificar', + 'Font Name': 'Nome da fonte', + 'Font Size': 'Tamanho da fonte', + 'Font Color': 'Cor da fonte', + 'Remove Formatting': 'Remover a formatação', + 'Cut': 'Cortar', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Seu navegador não permite o comando cortar. Por favor use o atalho Ctrl/Cmd-X', + 'Copy': 'Copiar', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Seu navegador não permite o comando copiar. Por favor use o atalho Ctrl/Cmd-C', + 'Paste': 'Colar', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Seu navegador não permite o comando colar. Por favor use o atalho Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Cole o seu texto dentro da caixa de texto a seguir:', + 'Paste Text': 'Colar o texto', + 'Bullet list': 'Lista com marcadores', + 'Numbered list': 'Lista numérica', + 'Undo': 'Desfazer', + 'Redo': 'Refazer', + 'Rows:': 'Linhas:', + 'Cols:': 'Colunas:', + 'Insert a table': 'Inserir uma tabela', + 'Insert a horizontal rule': 'Inserir uma linha horizontal', + 'Code': 'Código', + 'Width (optional):': 'Largura (opcional):', + 'Height (optional):': 'Altura (opcional):', + 'Insert an image': 'Inserir uma imagem', + 'E-mail:': 'E-mail:', + 'Insert an email': 'Inserir um e-mail', + 'URL:': 'URL:', + 'Insert a link': 'Inserir um hiperlink', + 'Unlink': 'Remover o hiperlink', + 'More': 'Mais', + 'Insert an emoticon': 'Inserir um emoticon', + 'Video URL:': 'Video URL:', + 'Insert': 'Inserir', + 'Insert a YouTube video': 'Inserir YouTube video', + 'Insert current date': 'Inserir a data atual', + 'Insert current time': 'Inserir a hora atual', + 'Print': 'Imprimir', + 'View source': 'Código fonte', + 'Description (optional):': 'Descrição (opcional):', + 'Enter the image URL:': 'Introduza o endereço URL da imagem:', + 'Enter the e-mail address:': 'Introduza o endereço de e-mail:', + 'Enter the displayed text:': 'Indique o texto exibido:', + 'Enter URL:': 'Introduza o endereço URL:', + 'Enter the YouTube video URL or ID:': 'Introduza o endereço URL ou o ID do video do YouTube:', + 'Insert a Quote': 'Inserir uma citação', + dateFormat: 'day/month/year' + }; + + // Set as the default Portuguese locale + sceditor.locale['pt'] = sceditor.locale['pt-PT']; +})(); diff --git a/public/assets/languages/ru.js b/public/assets/languages/ru.js new file mode 100644 index 0000000..f771e9e --- /dev/null +++ b/public/assets/languages/ru.js @@ -0,0 +1,57 @@ +(function () { + 'use strict'; + + sceditor.locale['ru'] = { + 'Bold': 'Жирный', + 'Italic': 'Курсив', + 'Underline': 'Подчёркнутый', + 'Strikethrough': 'Зачёркнутый', + 'Subscript': 'Нижний индекс', + 'Superscript': 'Верхний индекс', + 'Align left': 'Выравнивание по левому краю', + 'Center': 'Выравнивание по центру', + 'Align right': 'Выравнивание по правому краю', + 'Justify': 'Выравнивание по обоим краям', + 'Font Name': 'Шрифт', + 'Font Size': 'Размер шрифта', + 'Font Color': 'Цвет шрифта', + 'Remove Formatting': 'Удалить форматирование', + 'Cut': 'Вырезать', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ваш браузер не позволяет выполнять эту команду. Пожалуйста, используйте сочетание клавиш Ctrl / Cmd-X', + 'Copy': 'Копировать', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ваш браузер не позволяет выполнять эту команду. Пожалуйста, используйте сочетание клавиш Ctrl / Cmd-C', + 'Paste': 'Выставить', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ваш браузер не позволяет выполнять эту команду. Пожалуйста, используйте сочетание клавиш Ctrl / Cmd-V', + 'Paste your text inside the following box:': 'Вставьте текст в следующее окно:', + 'Paste Text': 'Вставить текст', + 'Bullet list': 'Маркированный список', + 'Numbered list': 'Нумерованный список', + 'Undo': 'Отменить', + 'Redo': 'Повторить', + 'Rows:': 'Строки', + 'Cols:': 'Столбцы', + 'Insert a table': 'Таблица', + 'Insert a horizontal rule': 'Горизонтальная линия', + 'Code': 'Код', + 'Insert a Quote': 'Цитата', + 'Width (optional):': 'Ширина (необязательно):', + 'Height (optional):': 'Высота (необязательно):', + 'Insert an image': 'Изображение', + 'E-mail:': 'E-mail', + 'Insert an email': 'E-mail', + 'URL:': 'URL:', + 'Insert a link': 'Ссылка', + 'Unlink': 'Удалить ссылку', + 'More': 'Больше', + 'Insert an emoticon': 'Смайлы', + 'Video URL:': 'Видео URL', + 'Insert': 'Вставить', + 'Insert a YouTube video': 'YouTube-видео', + 'Insert current date': 'Текущая дата', + 'Insert current time': 'Текущее время', + 'Print': 'Распечатать', + 'View source': 'Показать код', + 'Maximize': 'Развернуть', + dateFormat: 'day.month.year' + }; +})(); diff --git a/public/assets/languages/sv.js b/public/assets/languages/sv.js new file mode 100644 index 0000000..eae0442 --- /dev/null +++ b/public/assets/languages/sv.js @@ -0,0 +1,58 @@ +(function () { + 'use strict'; + + sceditor.locale['sv-SE'] = { + 'Bold': 'Fet', + 'Italic': 'Kursiv', + 'Underline': 'Understruken', + 'Strikethrough': 'Genomstruken', + 'Subscript': 'Nersänkt', + 'Superscript': 'Upphöjt', + 'Align left': 'Vänsterställ', + 'Center': 'Centrera', + 'Align right': 'Högerställ', + 'Justify': 'Normalt oppstilt', + 'Font Name': 'Teckensnitt', + 'Font Size': 'Teckenstorlek', + 'Font Color': 'Teckenfärg', + 'Remove Formatting': 'Ta bort formatering', + 'Cut': 'Klipp ut', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Din webbläsare kan inte utföra kommandot. Vänligen använd kortkommando Ctrl / Cmd-X', + 'Copy': 'Kopiera', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Din webbläsare kan inte utföra kommandot. Vänligen använd kortkommando Ctrl / Cmd-C', + 'Paste': 'Klista in', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Din webbläsare kan inte utföra kommandot. Vänligen använd kortkommando Ctrl / Cmd-V', + 'Paste your text inside the following box:': 'Klistra in din text i rutan:', + 'Paste Text': 'Klistra in text', + 'Bullet list': 'Lista', + 'Numbered list': 'Numrerad lista', + 'Undo': 'Ångra', + 'Redo': 'Gör om', + 'Rows:': 'Rader', + 'Cols:': 'Kolumner', + 'Insert a table': 'Infoga tabell', + 'Insert a horizontal rule': 'Infoga skiljestreck', + 'Code': 'Kod', + 'Width (optional):': 'Bredd (valfritt):', + 'Height (optional):': 'Höjd (valfritt):', + 'Insert an image': 'Infoga bild', + 'E-mail:': 'E-post', + 'Insert an email': 'Infoga e-post', + 'URL:': 'URL:', + 'Insert a link': 'Infoga länk', + 'Unlink': 'Ta bort länk', + 'More': 'Mer', + 'Insert an emoticon': 'Infoga smiley', + 'Video URL:': 'Video URL', + 'Insert': 'Infoga', + 'Insert a YouTube video': 'Infoga YouTube-video', + 'Insert current date': 'Infoga dagens datum', + 'Insert current time': 'Infoga nuvarande tid', + 'Print': 'Skriv ut', + 'View source': 'Visa källkod', + 'Description (optional):': 'Beskrivning (valfritt):', + dateFormat: 'year-month-day' + }; + + sceditor.locale['sv'] = sceditor.locale['sv-SE']; +})(); diff --git a/public/assets/languages/template.js b/public/assets/languages/template.js new file mode 100644 index 0000000..befe1c3 --- /dev/null +++ b/public/assets/languages/template.js @@ -0,0 +1,75 @@ +/** + * @author + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + // Replace with the language code, e.g. no, fr, en, ect. + sceditor.locale[''] = { + + // Original string is on the left, place the translation between + // the quotes on the right + 'Bold': '', + 'Italic': '', + 'Underline': '', + 'Strikethrough': '', + 'Subscript': '', + 'Superscript': '', + 'Align left': '', + 'Center': '', + 'Align right': '', + 'Justify': '', + 'Font Name': '', + 'Font Size': '', + 'Font Color': '', + 'Remove Formatting': '', + 'Cut': '', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': '', + 'Copy': '', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': '', + 'Paste': '', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': '', + 'Paste your text inside the following box:': '', + 'Paste Text': '', + 'Bullet list': '', + 'Numbered list': '', + 'Undo': '', + 'Redo': '', + 'Rows:': '', + 'Cols:': '', + 'Insert a table': '', + 'Insert a horizontal rule': '', + 'Code': '', + 'Width (optional):': '', + 'Height (optional):': '', + 'Insert an image': '', + 'E-mail:': '', + 'Insert an email': '', + 'URL:': '', + 'Insert a link': '', + 'Unlink': '', + 'More': '', + 'Insert an emoticon': '', + 'Video URL:': '', + 'Insert': '', + 'Insert a YouTube video': '', + 'Insert current date': '', + 'Insert current time': '', + 'Print': '', + 'View source': '', + 'Description (optional):': '', + 'Enter the image URL:': '', + 'Enter the e-mail address:': '', + 'Enter the displayed text:': '', + 'Enter URL:': '', + 'Enter the YouTube video URL or ID:': '', + 'Insert a Quote': '', + 'Invalid YouTube video': '', + 'Drop files here': '', + + // month format, replace - with the date format separator and order in the + // order used + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/tr.js b/public/assets/languages/tr.js new file mode 100644 index 0000000..325a2cc --- /dev/null +++ b/public/assets/languages/tr.js @@ -0,0 +1,66 @@ +/** + * @author Mahmut Yaman - iletisim@/m-yaman.com + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + sceditor.locale['tr'] = { + 'Bold': 'Kalın', + 'Italic': 'İtalik', + 'Underline': 'Altı çizgili', + 'Strikethrough': 'Üstü çizgili', + 'Subscript': 'Simge', + 'Superscript': 'Üstsimge', + 'Align left': 'Sola yasla', + 'Center': 'Ortala', + 'Align right': 'Sağa yasla', + 'Justify': 'Satır uzunluğuna ayarla', + 'Font Name': 'Yazı tipi', + 'Font Size': 'Yazı boyutu', + 'Font Color': 'Yazı rengi', + 'Remove Formatting': 'Biçimlendirmeyi temizle', + 'Cut': 'Kes', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Tarayıcınız kesme komutuna izin vermiyor. Lütfen Ctrl/Cmd-X klavye kısayolunu kullanın.', + 'Copy': 'Kopyala', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Tarayıcınız kopyalama komutuna izin vermiyor. Lütfen Ctrl/Cmd-C klavye kısayolunu kullanın.', + 'Paste': 'Yapıştır', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Tarayıcınız yapıştırma komutuna izin vermiyor. Lütfen Ctrl/Cmd-V klavye kısayolunu kullanın.', + 'Paste your text inside the following box:': 'Yazınızı bu kutucuğa yapıştırın:', + 'Paste Text': 'Metin Yapıştır', + 'Bullet list': 'Madde işaretli liste', + 'Numbered list': 'Numaralı liste', + 'Undo': 'Geri al', + 'Redo': 'Yinele', + 'Rows:': 'Sütun:', + 'Cols:': 'Kolon:', + 'Insert a table': 'Tablo ekle', + 'Insert a horizontal rule': 'Yatay ayraç ekle', + 'Code': 'Kod', + 'Width (optional):': 'Genişlik (opsiyonel):', + 'Height (optional):': 'Yükseklik (opsiyonel):', + 'Insert an image': 'Resim ekle', + 'E-mail:': 'E-posta:', + 'Insert an email': 'E-posta ekle', + 'URL:': 'URL:', + 'Insert a link': 'Bağlantı ekle', + 'Unlink': 'Bağlantıyı kaldır', + 'More': 'Daha fazla', + 'Insert an emoticon': 'Yüz ifadesi ekle', + 'Video URL:': 'Video URL:', + 'Insert': 'Ekle', + 'Insert a YouTube video': 'YouTube videosu ekle', + 'Insert current date': 'Şuanki tarihi ekle', + 'Insert current time': 'Şuanki saati ekle', + 'Print': 'Yazdır', + 'View source': 'Kaynağı görüntüle', + 'Description (optional):': 'Açıklama (opsiyonel):', + 'Enter the image URL:': 'Resim URL\'sini girin:', + 'Enter the e-mail address:': 'E-posta adresini girin:', + 'Enter the displayed text:': 'Görünecek yazıyı girin:', + 'Enter URL:': 'URL\'yi girin:', + 'Enter the YouTube video URL or ID:': 'YouTube video URL\'sini yada ID\'sini girin:', + 'Insert a Quote': 'Alıntı ekle', + 'Invalid YouTube video': 'Geçersiz YouTube videosu', + dateFormat: 'day-month-year' + }; +})(); diff --git a/public/assets/languages/tw.js b/public/assets/languages/tw.js new file mode 100644 index 0000000..8336912 --- /dev/null +++ b/public/assets/languages/tw.js @@ -0,0 +1,68 @@ +/** + * @author + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['tw'] = { + 'Bold': '粗體', + 'Italic': '斜體', + 'Underline': '底線', + 'Strikethrough': '删除線', + 'Subscript': '下標', + 'Superscript': '上標', + 'Align left': '靠左對齊', + 'Center': '置中', + 'Align right': '靠右對齊', + 'Justify': '兩端對齊', + 'Font Name': '字形', + 'Font Size': '字體大小', + 'Font Color': '文字顏色', + 'Remove Formatting': '清除格式', + 'Cut': '剪下', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': '您的瀏覽器不支持剪下命令,請使用快速键 Ctrl/Cmd-X', + 'Copy': '拷貝', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': '您的瀏覽器不支持拷貝命令,請使用快速键 Ctrl/Cmd-C', + 'Paste': '貼上', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': '您的瀏覽器不支持貼上命令,請使用快速键 Ctrl/Cmd-V', + 'Paste your text inside the following box:': '請在下面貼上您的文字', + 'Paste Text': '貼上纯文字', + 'Bullet list': '符號列表', + 'Numbered list': '编號列表', + 'Undo': '復原', + 'Redo': '重做', + 'Rows:': '行數', + 'Cols:': '列數', + 'Insert a table': '插入表格', + 'Insert a horizontal rule': '插入分隔線', + 'Code': '原始碼', + 'Width (optional):': '寬度(選填)', + 'Height (optional):': '高度(選填)', + 'Insert an image': '插入圖片', + 'E-mail:': 'Email', + 'Insert an email': '插入Email', + 'URL:': '網址', + 'Insert a link': '插入超鏈結', + 'Unlink': '取消超鏈結', + 'More': '更多', + 'Insert an emoticon': '插入表情符號', + 'Video URL:': '影片網址', + 'Insert': '插入', + 'Insert a YouTube video': '插入 YouTube 影片', + 'Insert current date': '插入目前日期', + 'Insert current time': '插入目前時間', + 'Print': '列印', + 'View source': '查看原始碼', + 'Description (optional):': '描述(選填)', + 'Enter the image URL:': '輸入圖片網址', + 'Enter the e-mail address:': '輸入 Email', + 'Enter the displayed text:': '輸入顯示文字', + 'Enter URL:': '輸入網址', + 'Enter the YouTube video URL or ID:': '輸入 YouTube 網址或影片编號', + 'Insert a Quote': '插入引用', + 'Invalid YouTube video': '無效的YouTube影片', + + dateFormat: 'year-month-day' + }; +})(); diff --git a/public/assets/languages/uk.js b/public/assets/languages/uk.js new file mode 100644 index 0000000..121f8c8 --- /dev/null +++ b/public/assets/languages/uk.js @@ -0,0 +1,57 @@ +(function () { + 'use strict'; + + sceditor.locale['uk'] = { + 'Bold': 'Жирний', + 'Italic': 'Курсив', + 'Underline': 'Підкреслений', + 'Strikethrough': 'Закреслений', + 'Subscript': 'Нижній індекс', + 'Superscript': 'Верхній індекс', + 'Align left': 'Вирівняти по лівому краю', + 'Center': 'Вирівняти по центру', + 'Align right': 'Вирівняти по правому краю', + 'Justify': 'Вирівняти по ширині', + 'Font Name': 'Шрифт', + 'Font Size': 'Розмір шрифту', + 'Font Color': 'Колір шрифту', + 'Remove Formatting': 'Видалити форматування', + 'Cut': 'Вирізати', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Ваш браузер не дозволяє виконати цю команду. Будь ласка, використовуйте комбінацію клавіш Ctrl/Cmd-X', + 'Copy': 'Копіювати', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Ваш браузер не дозволяє виконати цю команду. Будь ласка, використовуйте комбінацію клавіш Ctrl/Cmd-C', + 'Paste': 'Вставити', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Ваш браузер не дозволяє виконати цю команду. Будь ласка, використовуйте комбінацію клавіш Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Вставте текст у наступне вікно:', + 'Paste Text': 'Вставити текст', + 'Bullet list': 'Маркований список', + 'Numbered list': 'Нумерований список', + 'Undo': 'Відмінити', + 'Redo': 'Повторити', + 'Rows:': 'Рядків:', + 'Cols:': 'Cтовпців:', + 'Insert a table': 'Додати таблицю', + 'Insert a horizontal rule': 'Додати горизонтальну лінію', + 'Code': 'Код', + 'Insert a Quote': 'Додати цитату', + 'Width (optional):': 'Ширина (необов\'язково):', + 'Height (optional):': 'Висота (необов\'язково):', + 'Insert an image': 'Додати зображення', + 'E-mail:': 'E-mail:', + 'Insert an email': 'Додати E-mail', + 'URL:': 'URL:', + 'Insert a link': 'Додати посилання', + 'Unlink': 'Видалити посилання', + 'More': 'Більше', + 'Insert an emoticon': 'Додати смайлик', + 'Video URL:': 'URL відео:', + 'Insert': 'Вставити', + 'Insert a YouTube video': 'Додати відео з YouTube', + 'Insert current date': 'Додати дату', + 'Insert current time': 'Додати час', + 'Print': 'Надрукувати', + 'View source': 'Показати код', + 'Maximize': 'Розгорнути редактор', + dateFormat: 'day.month.year' + }; +})(); diff --git a/public/assets/languages/vi.js b/public/assets/languages/vi.js new file mode 100644 index 0000000..07d5bc8 --- /dev/null +++ b/public/assets/languages/vi.js @@ -0,0 +1,68 @@ +/** + * @author Chien + * @license [MIT](http://www.opensource.org/licenses/mit-license.php) + */ +(function () { + 'use strict'; + + sceditor.locale['vi'] = { + 'Bold': 'Đậm', + 'Italic': 'Nghiêng', + 'Underline': 'Gạch chân', + 'Strikethrough': 'Gạch giữa', + 'Subscript': 'Hệ số', + 'Superscript': 'Mũ', + 'Align left': 'Căn trái', + 'Center': 'Căn giữa', + 'Align right': 'Căn phải', + 'Justify': 'Căn đều', + 'Font Name': 'Phông chữ', + 'Font Size': 'Cỡ chữ', + 'Font Color': 'Màu chữ', + 'Remove Formatting': 'Xóa định dạng', + 'Cut': 'Cắt', + 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X': 'Trình duyệt không cho phép sử dụng lệnh Cut. Vui lòng sử dụng phím tắt Ctrl/Cmd-X', + 'Copy': 'Sao chép', + 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C': 'Trình duyệt không cho phép sử dụng lệnh Copy. Vui lòng sử dụng phím tắt Ctrl/Cmd-C', + 'Paste': 'Chép vào', + 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V': 'Trình duyệt không cho phép sử dụng lệnh Paste. Vui lòng sử dụng phím tắt Ctrl/Cmd-V', + 'Paste your text inside the following box:': 'Chép nội dung text vào khung sau', + 'Paste Text': 'Chép nội dung text', + 'Bullet list': 'Danh sách kiểu nốt', + 'Numbered list': 'Danh sách kiểu số', + 'Undo': 'Hủy bỏ', + 'Redo': 'Trở lại bước trước', + 'Rows:': 'Số dòng', + 'Cols:': 'Số cột', + 'Insert a table': 'Thêm bảng', + 'Insert a horizontal rule': 'Thêm thước ngang', + 'Code': 'Mã code', + 'Width (optional):': 'Dài (không bắt buộc)', + 'Height (optional):': 'Rộng (không bắt buộc)', + 'Insert an image': 'Chèn hình ảnh', + 'E-mail:': 'E-mail', + 'Insert an email': 'Chèn email', + 'URL:': 'Liên kết', + 'Insert a link': 'Chèn liên kết', + 'Unlink': 'Bỏ liên kết', + 'More': 'Xem thêm', + 'Insert an emoticon': 'Chèn biểu tượng', + 'Video URL:': 'Đường dẫn của Video', + 'Insert': 'Thêm vào', + 'Insert a YouTube video': 'Chèn Youtube', + 'Insert current date': 'Chèn ngày hiện tại', + 'Insert current time': 'Chèn thời gian hiện tại', + 'Print': 'In ấn', + 'View source': 'Xem mã nguồn', + 'Description (optional):': 'Mô tả (không bắt buộc)', + 'Enter the image URL:': 'Nhập vào đường dẫn của hình ảnh', + 'Enter the e-mail address:': 'Nhập vào địa chỉ email', + 'Enter the displayed text:': 'Nhập vào nội dung hiển thị', + 'Enter URL:': 'Nhập vào liên kết', + 'Enter the YouTube video URL or ID:': 'Nhập vào liên kết của video hoặc ID trên Youtube', + 'Insert a Quote': 'Chèn trích dẫn', + 'Invalid YouTube video': 'Video Youtube không chính xác', + + dateFormat: 'day/month/year' + }; +})(); diff --git a/public/assets/minified/formats/bbcode.js b/public/assets/minified/formats/bbcode.js new file mode 100644 index 0000000..e17f569 --- /dev/null +++ b/public/assets/minified/formats/bbcode.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(t){"use strict";var h=t.escapeEntities,a=t.escapeUriScheme,m=t.dom,e=t.utils,p=m.css,g=m.attr,v=m.is,n=e.extend,s=e.each,r=t.ie,b=r&&r<11,y="data-sceditor-emoticon",l=t.command.get,x={always:1,never:2,auto:3},i={bold:{txtExec:["[b]","[/b]"]},italic:{txtExec:["[i]","[/i]"]},underline:{txtExec:["[u]","[/u]"]},strike:{txtExec:["[s]","[/s]"]},subscript:{txtExec:["[sub]","[/sub]"]},superscript:{txtExec:["[sup]","[/sup]"]},left:{txtExec:["[left]","[/left]"]},center:{txtExec:["[center]","[/center]"]},right:{txtExec:["[right]","[/right]"]},justify:{txtExec:["[justify]","[/justify]"]},font:{txtExec:function(t){var e=this;l("font")._dropDown(e,t,function(t){e.insertText("[font="+t+"]","[/font]")})}},size:{txtExec:function(t){var e=this;l("size")._dropDown(e,t,function(t){e.insertText("[size="+t+"]","[/size]")})}},color:{txtExec:function(t){var e=this;l("color")._dropDown(e,t,function(t){e.insertText("[color="+t+"]","[/color]")})}},bulletlist:{txtExec:function(t,e){var n="";s(e.split(/\r?\n/),function(){n+=(n?"\n":"")+"[li]"+this+"[/li]"}),this.insertText("[ul]\n"+n+"\n[/ul]")}},orderedlist:{txtExec:function(t,e){var n="";s(e.split(/\r?\n/),function(){n+=(n?"\n":"")+"[li]"+this+"[/li]"}),this.insertText("[ol]\n"+n+"\n[/ol]")}},table:{txtExec:["[table][tr][td]","[/td][/tr][/table]"]},horizontalrule:{txtExec:["[hr]"]},code:{txtExec:["[code]","[/code]"]},image:{txtExec:function(t,e){var i=this;l("image")._dropDown(i,t,e,function(t,e,n){var r="";e&&(r+=" width="+e),n&&(r+=" height="+n),i.insertText("[img"+r+"]"+t+"[/img]")})}},email:{txtExec:function(t,n){var r=this;l("email")._dropDown(r,t,function(t,e){r.insertText("[email="+t+"]"+(e||n||t)+"[/email]")})}},link:{txtExec:function(t,n){var r=this;l("link")._dropDown(r,t,function(t,e){r.insertText("[url="+t+"]"+(e||n||t)+"[/url]")})}},quote:{txtExec:["[quote]","[/quote]"]},youtube:{txtExec:function(t){var e=this;l("youtube")._dropDown(e,t,function(t){e.insertText("[youtube]"+t+"[/youtube]")})}},rtl:{txtExec:["[rtl]","[/rtl]"]},ltr:{txtExec:["[ltr]","[/ltr]"]}},k={b:{tags:{b:null,strong:null},styles:{"font-weight":["bold","bolder","401","700","800","900"]},format:"[b]{0}[/b]",html:"{0}"},i:{tags:{i:null,em:null},styles:{"font-style":["italic","oblique"]},format:"[i]{0}[/i]",html:"{0}"},u:{tags:{u:null},styles:{"text-decoration":["underline"]},format:"[u]{0}[/u]",html:"{0}"},s:{tags:{s:null,strike:null},styles:{"text-decoration":["line-through"]},format:"[s]{0}[/s]",html:"{0}"},sub:{tags:{sub:null},format:"[sub]{0}[/sub]",html:"{0}"},sup:{tags:{sup:null},format:"[sup]{0}[/sup]",html:"{0}"},font:{tags:{font:{face:null}},styles:{"font-family":null},quoteType:x.never,format:function(t,e){var n;return v(t,"font")&&(n=g(t,"face"))||(n=p(t,"font-family")),"[font="+E(n)+"]"+e+"[/font]"},html:'{0}'},size:{tags:{font:{size:null}},styles:{"font-size":null},format:function(t,e){var n=g(t,"size"),r=2;return n||(n=p(t,"fontSize")),-1{!0}'},color:{tags:{font:{color:null}},styles:{color:null},quoteType:x.never,format:function(t,e){var n;return v(t,"font")&&(n=g(t,"color"))||(n=t.style.color||p(t,"color")),"[color="+c(n)+"]"+e+"[/color]"},html:function(t,e,n){return''+n+""}},ul:{tags:{ul:null},breakStart:!0,isInline:!1,skipLastLineBreak:!0,format:"[ul]{0}[/ul]",html:"
      {0}
    "},list:{breakStart:!0,isInline:!1,skipLastLineBreak:!0,html:"
      {0}
    "},ol:{tags:{ol:null},breakStart:!0,isInline:!1,skipLastLineBreak:!0,format:"[ol]{0}[/ol]",html:"
      {0}
    "},li:{tags:{li:null},isInline:!1,closedBy:["/ul","/ol","/list","*","li"],format:"[li]{0}[/li]",html:"
  • {0}
  • "},"*":{isInline:!1,closedBy:["/ul","/ol","/list","*","li"],html:"
  • {0}
  • "},table:{tags:{table:null},isInline:!1,isHtmlInline:!0,skipLastLineBreak:!0,format:"[table]{0}[/table]",html:"{0}
    "},tr:{tags:{tr:null},isInline:!1,skipLastLineBreak:!0,format:"[tr]{0}[/tr]",html:"{0}"},th:{tags:{th:null},allowsEmpty:!0,isInline:!1,format:"[th]{0}[/th]",html:"{0}"},td:{tags:{td:null},allowsEmpty:!0,isInline:!1,format:"[td]{0}[/td]",html:"{0}"},emoticon:{allowsEmpty:!0,tags:{img:{src:null,"data-sceditor-emoticon":null}},format:function(t,e){return g(t,y)+e},html:"{0}"},hr:{tags:{hr:null},allowsEmpty:!0,isSelfClosing:!0,isInline:!1,format:"[hr]{0}",html:"
    "},img:{allowsEmpty:!0,tags:{img:{src:null}},allowedChildren:["#"],quoteType:x.never,format:function(e,t){var n,r,i="",l=function(t){return e.style?e.style[t]:null};return g(e,y)?t:(n=g(e,"width")||l("width"),r=g(e,"height")||l("height"),(e.complete&&(n||r)||n&&r)&&(i="="+m.width(e)+"x"+m.height(e)),"[img"+i+"]"+g(e,"src")+"[/img]")},html:function(t,e,n){var r,i,l,o="";return r=e.width,i=e.height,e.defaultattr&&(r=(l=e.defaultattr.split(/x/i))[0],i=2===l.length?l[1]:l[0]),void 0!==r&&(o+=' width="'+h(r,!0)+'"'),void 0!==i&&(o+=' height="'+h(i,!0)+'"'),"'}},url:{allowsEmpty:!0,tags:{a:{href:null}},quoteType:x.never,format:function(t,e){var n=g(t,"href");return"mailto:"===n.substr(0,7)?'[email="'+n.substr(7)+'"]'+e+"[/email]":"[url="+n+"]"+e+"[/url]"},html:function(t,e,n){return e.defaultattr=h(e.defaultattr,!0)||n,''+n+""}},email:{quoteType:x.never,html:function(t,e,n){return''+n+""}},quote:{tags:{blockquote:null},isInline:!1,quoteType:x.never,format:function(t,e){for(var n,r="data-author",i="",l=t.children,o=0;!n&&o"+n),"
    "+n+"
    "}},code:{tags:{code:null},isInline:!1,allowedChildren:["#","#newline"],format:"[code]{0}[/code]",html:"{0}"},left:{styles:{"text-align":["left","-webkit-left","-moz-left","-khtml-left"]},isInline:!1,format:"[left]{0}[/left]",html:'
    {0}
    '},center:{styles:{"text-align":["center","-webkit-center","-moz-center","-khtml-center"]},isInline:!1,format:"[center]{0}[/center]",html:'
    {0}
    '},right:{styles:{"text-align":["right","-webkit-right","-moz-right","-khtml-right"]},isInline:!1,format:"[right]{0}[/right]",html:'
    {0}
    '},justify:{styles:{"text-align":["justify","-webkit-justify","-moz-justify","-khtml-justify"]},isInline:!1,format:"[justify]{0}[/justify]",html:'
    {0}
    '},youtube:{allowsEmpty:!0,tags:{iframe:{"data-youtube-id":null}},format:function(t,e){return(t=g(t,"data-youtube-id"))?"[youtube]"+t+"[/youtube]":e},html:''},rtl:{styles:{direction:["rtl"]},isInline:!1,format:"[rtl]{0}[/rtl]",html:'
    {0}
    '},ltr:{styles:{direction:["ltr"]},isInline:!1,format:"[ltr]{0}[/ltr]",html:'
    {0}
    '},ignore:{}};function w(t,r){return t.replace(/\{([^}]+)\}/g,function(t,e){var n=!0;return"!"===e.charAt(0)&&(n=!1,e=e.substring(1)),"0"===e&&(n=!1),void 0===r[e]?t:n?h(r[e],!0):r[e]})}function B(t){return"function"==typeof t}function E(t){return t?t.replace(/\\(.)/g,"$1").replace(/^(["'])(.*?)\1$/,"$2"):t}function C(t){var n=arguments;return t.replace(/\{(\d+)\}/g,function(t,e){return void 0!==n[e-0+1]?n[e-0+1]:"{"+e+"}"})}var I="open",T="content",S="newline",L="close";function u(t,e,n,r,i,l){var o=this;o.type=t,o.name=e,o.val=n,o.attrs=r||{},o.children=i||[],o.closing=l||null}function q(t){var m=this;function o(t,e){var n,r,i;return t===I&&(n=e.match(/\[([^\]\s=]+)(?:([^\]]+))?\]/))&&(i=l(n[1]),n[2]&&(n[2]=n[2].trim())&&(r=function(t){var e,n=/([^\s=]+)=(?:(?:(["'])((?:\\\2|[^\2])*?)\2)|((?:.(?!\s\S+=))*.))/g,r={};if("="===t.charAt(0)&&t.indexOf("=",1)<0)r.defaultattr=E(t.substr(1));else for("="===t.charAt(0)&&(t="defaultattr"+t);e=n.exec(t);)r[l(e[1])]=E(e[3])||e[4];return r}(n[2]))),t===L&&(n=e.match(/\[\/([^\[\]]+)\]/))&&(i=l(n[1])),t===S&&(i="#newline"),i&&(t!==I&&t!==L||k[i])||(t=T,i="#"),new u(t,i,e,r)}function d(t,e,n){for(var r=n.length;r--;)if(n[r].type===e&&n[r].name===t)return!0;return!1}function p(t,e){var n=(t?k[t.name]:{}).allowedChildren;return!m.opts.fixInvalidChildren||!n||-1"),B(i.html)?o=i.html.call(m,r,r.attrs,l):(r.attrs[0]=l,o=w(i.html,r.attrs))):o=r.val+l+(r.closing?r.closing.val:"");else{if(r.type===S){if(!n){f.push("
    ");continue}s||f.push("
    "),b||f.push("
    "),e.length||f.push("
    "),f.push("
    \n"),s=!1;continue}a=n,o=h(r.val,!0)}a&&!s?(f.push("
    "),s=!0):!a&&s&&(f.push("
    \n"),s=!1),f.push(o)}s&&f.push("
    \n");return f.join("")}(m.parse(t,e),!0)},m.toBBCode=function(t,e){return function t(e){var n,r,i,l,o,a,s,u,c,f,h=[];for(;0\n/g,"
    "),(i=a.firstChild)&&v(i,"div")&&o(i,!0),(i=a.lastChild)&&v(i,"div")&&o(i),a.innerHTML):s}function e(t,e,n,r){var i,l,o=(n=n||document).createElement("div"),a=n.createElement("div"),s=new q(u.opts.parserOptions);for(a.innerHTML=e,p(o,"visibility","hidden"),o.appendChild(a),n.body.appendChild(o),t&&(o.insertBefore(n.createTextNode("#"),o.firstChild),o.appendChild(n.createTextNode("#"))),r&&p(a,"whiteSpace",p(r,"whiteSpace")),l=a.getElementsByClassName("sceditor-ignore");l.length;)l[0].parentNode.removeChild(l[0]);return m.removeWhiteSpace(o),i=d(a),n.body.removeChild(o),i=s.toBBCode(i,!0),u.opts.bbcodeTrim&&(i=i.trim()),i}u.init=function(){u.opts=this.opts,u.elementToBbcode=d,s(k,function(n){var r,t=k[n].tags,e=k[n].styles;t&&s(t,function(t,e){r=!1===k[n].isInline,o[t]=o[t]||{},o[t][r]=o[t][r]||{},o[t][r][n]=e}),e&&s(e,function(t,e){r=!1===k[n].isInline,a[r]=a[r]||{},a[r][t]=a[r][t]||{},a[r][t][n]=e})}),this.commands=n(!0,{},i,this.commands),this.toBBCode=u.toSource,this.fromBBCode=u.toHtml},u.toHtml=t.bind(null,!1),u.fragmentToHtml=t.bind(null,!0),u.toSource=e.bind(null,!1),u.fragmentToSource=e.bind(null,!0)}u.prototype={clone:function(){var t=this;return new u(t.type,t.name,t.val,n({},t.attrs),[],t.closing?t.closing.clone():null)},splitAt:function(t){var e,n=this.clone(),r=this.children.indexOf(t);return-1",""]},italic:{txtExec:["",""]},underline:{txtExec:['',""]},strike:{txtExec:['',""]},subscript:{txtExec:["",""]},superscript:{txtExec:["",""]},left:{txtExec:['
    ',"
    "]},center:{txtExec:['
    ',"
    "]},right:{txtExec:['
    ',"
    "]},justify:{txtExec:['
    ',"
    "]},font:{txtExec:function(t){var e=this;l("font")._dropDown(e,t,function(t){e.insertText('',"")})}},size:{txtExec:function(t){var e=this;l("size")._dropDown(e,t,function(t){e.insertText('',"")})}},color:{txtExec:function(t){var e=this;l("color")._dropDown(e,t,function(t){e.insertText('',"")})}},bulletlist:{txtExec:["
    • ","
    "]},orderedlist:{txtExec:["
    1. ","
    "]},table:{txtExec:["
    ","
    "]},horizontalrule:{txtExec:["
    "]},code:{txtExec:["",""]},image:{txtExec:function(t,e){var o=this;l("image")._dropDown(o,t,e,function(t,e,n){var i="";e&&(i+=' width="'+e+'"'),n&&(i+=' height="'+n+'"'),o.insertText("')})}},email:{txtExec:function(t,n){var i=this;l("email")._dropDown(i,t,function(t,e){i.insertText(''+(e||n||t)+"")})}},link:{txtExec:function(t,n){var i=this;l("link")._dropDown(i,t,function(t,e){i.insertText(''+(e||n||t)+"")})}},quote:{txtExec:["
    ","
    "]},youtube:{txtExec:function(t){var n=this;l("youtube")._dropDown(n,t,function(t,e){n.insertText('')})}},rtl:{txtExec:['
    ',"
    "]},ltr:{txtExec:['
    ',"
    "]}};function z(){var o=this,n={},p={};function t(t,e,n){var i,o,h,r,a,l,s,c,u,d,f,g,v,x,m=n.createElement("div");return m.innerHTML=e,S(m,"visibility","hidden"),n.body.appendChild(m),o=m,C.traverse(o,function(t){var e=t.nodeName.toLowerCase();b("*",t),b(e,t)},!0),h=m,C.traverse(h,function(t){var e,n=t.nodeName.toLowerCase(),i=t.parentNode,o=t.nodeType,r=!C.isInline(t),a=t.previousSibling,l=t.nextSibling,s=i===h,c=!a&&!l,u="iframe"!==n&&function t(e,n){var i,o=e.childNodes,r=e.nodeName.toLowerCase(),a=e.nodeValue,l=o.length,s=z.allowedEmptyTags||[];if(n&&"br"===r)return!0;if(T(e,".sceditor-ignore"))return!0;if(-1":">",'"':"""," ":" "};return t?t.replace(/[&<>"\xa0]/g,function(t){return e[t]||t}):""}function g(t,e){switch(t.nodeType){case 1:"!"===t.nodeName.toLowerCase()?n(t):function(t,e){var n,i,o,r=t.nodeName.toLowerCase(),a="iframe"===r,l=t.attributes.length,s=t.firstChild,c=e||/pre(?:\-wrap)?$/i.test(S(t,"whiteSpace")),u=!t.firstChild&&!C.canHaveChildren(t)&&!a;if(T(t,".sceditor-ignore"))return;v("<"+r,!e&&h(t));for(;l--;)i=t.attributes[l],o=i.value,v(" "+i.name.toLowerCase()+'="'+f(o)+'"',!1);v(u?" />":">",!1),a||(n=s);for(;n;)d++,g(n,c),n=n.nextSibling,d--;u||v("",!c&&!a&&h(t)&&s&&h(s))}(t,e);break;case 3:!function(t,e){var n=t.nodeValue;e||(n=n.replace(/[\r\n]/," ").replace(/[^\S|\u00A0]+/g," "));n&&v(f(n),!e&&h(t))}(t,e);break;case 4:v("");break;case 8:n(t);break;case 9:case 11:!function(t){var e=t.firstChild;for(;e;)g(e),e=e.nextSibling}(t)}}function n(t){v("\x3c!-- "+f(t.nodeValue)+" --\x3e")}function v(t,e){var n=d;if(!1!==e)for(o.length&&o.push("\n");n--;)o.push(i.indentStr);o.push(t)}function h(t){var e=t.previousSibling;return 1!==t.nodeType&&e?!C.isInline(e):!e&&!C.isInline(t.parentNode)||!C.isInline(t)}this.serialize=function(t,e){if(o=[],e)for(t=t.firstChild;t;)g(t),t=t.nextSibling;else g(t);return o.join("")}},z.converters=[{tags:{"*":{width:null}},conv:function(t){S(t,"width",n(t,"width")),A(t,"width")}},{tags:{"*":{height:null}},conv:function(t){S(t,"height",n(t,"height")),A(t,"height")}},{tags:{li:{value:null}},conv:function(t){A(t,"value")}},{tags:{"*":{text:null}},conv:function(t){S(t,"color",n(t,"text")),A(t,"text")}},{tags:{"*":{color:null}},conv:function(t){S(t,"color",n(t,"color")),A(t,"color")}},{tags:{"*":{face:null}},conv:function(t){S(t,"fontFamily",n(t,"face")),A(t,"face")}},{tags:{"*":{align:null}},conv:function(t){S(t,"textAlign",n(t,"align")),A(t,"align")}},{tags:{"*":{border:null}},conv:function(t){S(t,"borderWidth",n(t,"border")),A(t,"border")}},{tags:{applet:{name:null},img:{name:null},layer:{name:null},map:{name:null},object:{name:null},param:{name:null}},conv:function(t){n(t,"id")||n(t,"id",n(t,"name")),A(t,"name")}},{tags:{"*":{vspace:null}},conv:function(t){S(t,"marginTop",n(t,"vspace")-0),S(t,"marginBottom",n(t,"vspace")-0),A(t,"vspace")}},{tags:{"*":{hspace:null}},conv:function(t){S(t,"marginLeft",n(t,"hspace")-0),S(t,"marginRight",n(t,"hspace")-0),A(t,"hspace")}},{tags:{hr:{noshade:null}},conv:function(t){S(t,"borderStyle","solid"),A(t,"noshade")}},{tags:{"*":{nowrap:null}},conv:function(t){S(t,"whiteSpace","nowrap"),A(t,"nowrap")}},{tags:{big:null},conv:function(t){S(i(t,"span"),"fontSize","larger")}},{tags:{small:null},conv:function(t){S(i(t,"span"),"fontSize","smaller")}},{tags:{b:null},conv:function(t){i(t,"strong")}},{tags:{u:null},conv:function(t){S(i(t,"span"),"textDecoration","underline")}},{tags:{s:null,strike:null},conv:function(t){S(i(t,"span"),"textDecoration","line-through")}},{tags:{dir:null},conv:function(t){i(t,"ul")}},{tags:{center:null},conv:function(t){S(i(t,"div"),"textAlign","center")}},{tags:{font:{size:null}},conv:function(t){S(t,"fontSize",S(t,"fontSize")),A(t,"size")}},{tags:{font:null},conv:function(t){i(t,"span")}},{tags:{"*":{type:["_moz"]}},conv:function(t){A(t,"type")}},{tags:{"*":{_moz_dirty:null}},conv:function(t){A(t,"_moz_dirty")}},{tags:{"*":{_moz_editor_bogus_node:null}},conv:function(t){t.parentNode.removeChild(t)}}],z.allowedAttribs={},z.disallowedAttribs={},z.allowedTags=[],z.disallowedTags=[],z.allowedEmptyTags=[],y.formats.xhtml=z}(sceditor); \ No newline at end of file diff --git a/public/assets/minified/icons/material.js b/public/assets/minified/icons/material.js new file mode 100644 index 0000000..ebc758a --- /dev/null +++ b/public/assets/minified/icons/material.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(H,V){"use strict";var t=V.dom,M={bold:'',bulletlist:'',center:'',code:'',color:'',copy:'',cut:'',date:'',email:'',emoticon:'',font:'',format:'',grip:'',horizontalrule:'',image:'',indent:'',italic:'',justify:'\t\t',left:'\t\t',link:'',ltr:'',maximize:'',orderedlist:'',outdent:'',paste:'',pastetext:'',print:'',quote:'',redo:'',removeformat:'',right:'',rtl:'',size:'',source:'',strike:'',subscript:'',superscript:'',table:'',time:'',underline:'',undo:'',unlink:'',youtube:''};V.icons.material=function(){var L,A={};return{create:function(H){return H in M&&(A[H]=V.dom.parseHTML(''+M[H]+"").firstChild,"color"===H&&(L=A[H].querySelector(".sce-color"))),A[H]},update:function(H,V){if(L){var M="inherit";!H&&V&&(M=V.ownerDocument.queryCommandValue("forecolor"),parseInt(M)===M&&(M="#"+("000000"+(M=(255&M)<<16|65280&M|(16711680&M)>>>16).toString(16)).slice(-6))),t.css(L,"fill",M)}},rtl:function(H){var V=A.grip;if(V){var M=H?"scaleX(-1)":"";t.css(V,"transform",M),t.css(V,"msTransform",M),t.css(V,"webkitTransform",M)}}}},V.icons.material.icons=M}(document,sceditor); \ No newline at end of file diff --git a/public/assets/minified/icons/monocons.js b/public/assets/minified/icons/monocons.js new file mode 100644 index 0000000..4cf24d3 --- /dev/null +++ b/public/assets/minified/icons/monocons.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(h,a){"use strict";var c=a.dom,v={bold:'B',bulletlist:'',center:'',code:'',color:'A',copy:'',cut:'',date:'',email:'',emoticon:'',font:'',format:'',grip:'',horizontalrule:'',image:'',indent:'',italic:'i',justify:'',left:'',link:'',ltr:'',maximize:'',orderedlist:'',outdent:'',paste:'',pastetext:'',print:'',quote:'',redo:'',removeformat:'',right:'',rtl:'',size:'',source:'',strike:'S',subscript:'',superscript:'',table:'',time:'',underline:'U',undo:'',unlink:'',youtube:''};a.icons.monocons=function(){var z,t={};return{create:function(h){return h in v&&(t[h]=a.dom.parseHTML(''+v[h]+"").firstChild,"color"===h&&(z=t[h].querySelector(".sce-color"))),t[h]},update:function(h,a){if(z){var v="inherit";!h&&a&&(v=a.ownerDocument.queryCommandValue("forecolor"),parseInt(v)===v&&(v="#"+("000000"+(v=(255&v)<<16|65280&v|(16711680&v)>>>16).toString(16)).slice(-6))),c.css(z,"fill",v)}},rtl:function(h){var a=t.grip;if(a){var v=h?"scaleX(-1)":"";c.css(a,"transform",v),c.css(a,"msTransform",v),c.css(a,"webkitTransform",v)}}}},a.icons.monocons.icons=v}(document,sceditor); \ No newline at end of file diff --git a/public/assets/minified/jquery.sceditor.bbcode.min.js b/public/assets/minified/jquery.sceditor.bbcode.min.js new file mode 100644 index 0000000..5c8e6f0 --- /dev/null +++ b/public/assets/minified/jquery.sceditor.bbcode.min.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(o){"use strict";function e(e,t){return typeof t===e}o=o&&o.hasOwnProperty("default")?o.default:o;var be=e.bind(null,"string"),xe=e.bind(null,"undefined"),we=e.bind(null,"function"),r=e.bind(null,"number");function t(e){return!Object.keys(e).length}function ke(e,t){for(var n=e===!!e,o=n?2:1,r=n?t:e,i=!!n&&e;o":">"," ":"  ","\r\n":"
    ","\r":"
    ","\n":"
    "};return!1!==t&&(n['"']=""",n["'"]="'",n["`"]="`"),e.replace(/ {2}|\r\n|[&<>\r\n'"`]/g,function(e){return n[e]||e})}var b={html:'

    ',toolbarButton:'
    {dispName}
    ',emoticon:'{key}',fontOpt:'{font}',sizeOpt:'{size}',pastetext:'
    ',table:'
    ',image:'
    ',email:'
    ',link:'
    ',youtubeMenu:'
    ',youtube:''};function ht(e,t,n){var o=b[e];return Object.keys(t).forEach(function(e){o=o.replace(new RegExp(ft("{"+e+"}"),"g"),t[e])}),n&&(o=et(o)),o}var x=v&&v<11;function w(e){if("mozHidden"in document)for(var t,n=e.getBody();n;){if((t=n).firstChild)t=t.firstChild;else{for(;t&&!t.nextSibling;)t=t.parentNode;t&&(t=t.nextSibling)}3===n.nodeType&&/[\n\r\t]+/.test(n.nodeValue)&&(/^pre/.test(je(n.parentNode,"whiteSpace"))||Be(n)),n=t}}var mt={bold:{exec:"bold",tooltip:"Bold",shortcut:"Ctrl+B"},italic:{exec:"italic",tooltip:"Italic",shortcut:"Ctrl+I"},underline:{exec:"underline",tooltip:"Underline",shortcut:"Ctrl+U"},strike:{exec:"strikethrough",tooltip:"Strikethrough"},subscript:{exec:"subscript",tooltip:"Subscript"},superscript:{exec:"superscript",tooltip:"Superscript"},left:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===je(e,"direction"),n=je(e,"textAlign");return"left"===n||n===(t?"start":"end")}},exec:"justifyleft",tooltip:"Align left"},center:{exec:"justifycenter",tooltip:"Center"},right:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===je(e,"direction"),n=je(e,"textAlign");return"right"===n||n===(t?"end":"start")}},exec:"justifyright",tooltip:"Align right"},justify:{exec:"justifyfull",tooltip:"Justify"},font:{_dropDown:function(t,e,n){var o=De("div");Fe(o,"click","a",function(e){n(Pe(this,"font")),t.closeDropDown(!0),e.preventDefault()}),t.opts.fonts.split(",").forEach(function(e){Re(o,ht("fontOpt",{font:e},!0))}),t.createDropDown(e,"font-picker",o)},exec:function(e){var t=this;mt.font._dropDown(t,e,function(e){t.execCommand("fontname",e)})},tooltip:"Font Name"},size:{_dropDown:function(t,e,n){var o=De("div");Fe(o,"click","a",function(e){n(Pe(this,"size")),t.closeDropDown(!0),e.preventDefault()});for(var r=1;r<=7;r++)Re(o,ht("sizeOpt",{size:r},!0));t.createDropDown(e,"fontsize-picker",o)},exec:function(e){var t=this;mt.size._dropDown(t,e,function(e){t.execCommand("fontsize",e)})},tooltip:"Font Size"},color:{_dropDown:function(t,e,n){var o=De("div"),r="",i=mt.color;i._htmlCache||(t.opts.colors.split("|").forEach(function(e){r+='
    ',e.split(",").forEach(function(e){r+=''}),r+="
    "}),i._htmlCache=r),Re(o,et(i._htmlCache)),Fe(o,"click","a",function(e){n(Pe(this,"color")),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"color-picker",o)},exec:function(e){var t=this;mt.color._dropDown(t,e,function(e){t.execCommand("forecolor",e)})},tooltip:"Font Color"},removeformat:{exec:"removeformat",tooltip:"Remove Formatting"},cut:{exec:"cut",tooltip:"Cut",errorMessage:"Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"},copy:{exec:"copy",tooltip:"Copy",errorMessage:"Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"},paste:{exec:"paste",tooltip:"Paste",errorMessage:"Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"},pastetext:{exec:function(e){var t,n=De("div"),o=this;Re(n,ht("pastetext",{label:o._("Paste your text inside the following box:"),insert:o._("Insert")},!0)),Fe(n,"click",".button",function(e){(t=He(n,"#txt")[0].value)&&o.wysiwygEditorInsertText(t),o.closeDropDown(!0),e.preventDefault()}),o.createDropDown(e,"pastetext",n)},tooltip:"Paste Text"},bulletlist:{exec:function(){w(this),this.execCommand("insertunorderedlist")},tooltip:"Bullet list"},orderedlist:{exec:function(){w(this),this.execCommand("insertorderedlist")},tooltip:"Numbered list"},indent:{state:function(e,t){var n,o,r;return qe(t,"li")?0:qe(t,"ul,ol,menu")&&(o=(n=this.getRangeHelper().selectedRange()).startContainer.parentNode,r=n.endContainer.parentNode,o!==o.parentNode.firstElementChild||qe(r,"li")&&r!==r.parentNode.lastElementChild)?0:-1},exec:function(){var e=this.getRangeHelper().getFirstBlockParent();this.focus(),Ne(e,"ul,ol,menu")&&this.execCommand("indent")},tooltip:"Add indent"},outdent:{state:function(e,t){return Ne(t,"ul,ol,menu")?0:-1},exec:function(){Ne(this.getRangeHelper().getFirstBlockParent(),"ul,ol,menu")&&this.execCommand("outdent")},tooltip:"Remove one indent"},table:{exec:function(e){var r=this,i=De("div");Re(i,ht("table",{rows:r._("Rows:"),cols:r._("Cols:"),insert:r._("Insert")},!0)),Fe(i,"click",".button",function(e){var t=Number(He(i,"#rows")[0].value),n=Number(He(i,"#cols")[0].value),o="";0"+Array(n+1).join("")+""),o+="
    "+(x?"":"
    ")+"
    ",r.wysiwygEditorInsertHtml(o),r.closeDropDown(!0),e.preventDefault())}),r.createDropDown(e,"inserttable",i)},tooltip:"Insert a table"},horizontalrule:{exec:"inserthorizontalrule",tooltip:"Insert a horizontal rule"},code:{exec:function(){this.wysiwygEditorInsertHtml("",(x?"":"
    ")+"
    ")},tooltip:"Code"},image:{_dropDown:function(t,e,n,o){var r=De("div");Re(r,ht("image",{url:t._("URL:"),width:t._("Width (optional):"),height:t._("Height (optional):"),insert:t._("Insert")},!0));var i=He(r,"#image")[0];i.value=n,Fe(r,"click",".button",function(e){i.value&&o(i.value,He(r,"#width")[0].value,He(r,"#height")[0].value),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"insertimage",r)},exec:function(e){var r=this;mt.image._dropDown(r,e,"",function(e,t,n){var o="";t&&(o+=' width="'+t+'"'),n&&(o+=' height="'+n+'"'),r.wysiwygEditorInsertHtml("')})},tooltip:"Insert an image"},email:{_dropDown:function(n,e,o){var r=De("div");Re(r,ht("email",{label:n._("E-mail:"),desc:n._("Description (optional):"),insert:n._("Insert")},!0)),Fe(r,"click",".button",function(e){var t=He(r,"#email")[0].value;t&&o(t,He(r,"#des")[0].value),n.closeDropDown(!0),e.preventDefault()}),n.createDropDown(e,"insertemail",r)},exec:function(e){var n=this;mt.email._dropDown(n,e,function(e,t){n.focus(),!n.getRangeHelper().selectedHtml()||t?n.wysiwygEditorInsertHtml(''+(t||e)+""):n.execCommand("createlink","mailto:"+e)})},tooltip:"Insert an email"},link:{_dropDown:function(t,e,n){var o=De("div");Re(o,ht("link",{url:t._("URL:"),desc:t._("Description (optional):"),ins:t._("Insert")},!0));var r=He(o,"#link")[0];function i(e){r.value&&n(r.value,He(o,"#des")[0].value),t.closeDropDown(!0),e.preventDefault()}Fe(o,"click",".button",i),Fe(o,"keypress",function(e){13===e.which&&r.value&&i(e)},Ie),t.createDropDown(e,"insertlink",o)},exec:function(e){var n=this;mt.link._dropDown(n,e,function(e,t){n.focus(),t||!n.getRangeHelper().selectedHtml()?(t=t||e,n.wysiwygEditorInsertHtml(''+t+"")):n.execCommand("createlink",e)})},tooltip:"Insert a link"},unlink:{state:function(){return Ne(this.currentNode(),"a")?0:-1},exec:function(){var e=Ne(this.currentNode(),"a");if(e){for(;e.firstChild;)Ve(e.firstChild,e);Be(e)}},tooltip:"Unlink"},quote:{exec:function(e,t,n){var o="
    ",r="
    ";t?(o=o+(n=n?""+n+"":"")+t+r,r=null):""===this.getRangeHelper().selectedHtml()&&(r=(x?"":"
    ")+r),this.wysiwygEditorInsertHtml(o,r)},tooltip:"Insert a Quote"},emoticon:{exec:function(f){var p=this,h=function(e){var t,n,o=p.opts,r=o.emoticonsRoot||"",i=o.emoticonsCompat,l=p.getRangeHelper(),a=i&&" "!==l.getOuterText(!0,1)?" ":"",s=i&&" "!==l.getOuterText(!1,1)?" ":"",c=De("div"),u=De("div"),d=ke({},o.emoticons.dropdown,e?o.emoticons.more:{});return Re(c,u),n=Math.sqrt(Object.keys(d).length),Fe(c,"click","img",function(e){p.insert(a+Ae(this,"alt")+s,null,!1).closeDropDown(!0),e.preventDefault()}),Ee(d,function(e,t){Re(u,De("img",{src:r+(t.url||t),alt:e,title:t.tooltip||e})),u.children.length>=n&&(u=De("div"),Re(c,u))}),!e&&o.emoticons.more&&(Re(t=De("a",{className:"sceditor-more"}),document.createTextNode(p._("More"))),Fe(t,"click",function(e){p.createDropDown(f,"more-emoticons",h(!0)),e.preventDefault()}),Re(c,t)),c};p.createDropDown(f,"emoticons",h(!1))},txtExec:function(e){mt.emoticon.exec.call(this,e)},tooltip:"Insert an emoticon"},youtube:{_dropDown:function(i,e,l){var a=De("div");Re(a,ht("youtubeMenu",{label:i._("Video URL:"),insert:i._("Insert")},!0)),Fe(a,"click",".button",function(e){var t=He(a,"#link")[0].value,n=t.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/),o=t.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/),r=0;o&&Ee(o[1].split(/[hms]/),function(e,t){""!==t&&(r=60*r+Number(t))}),n&&/^[a-zA-Z0-9_\-]{11}$/.test(n[1])&&l(n[1],r),i.closeDropDown(!0),e.preventDefault()}),i.createDropDown(e,"insertlink",a)},exec:function(e){var n=this;mt.youtube._dropDown(n,e,function(e,t){n.wysiwygEditorInsertHtml(ht("youtube",{id:e,time:t}))})},tooltip:"Insert a YouTube video"},date:{_date:function(e){var t=new Date,n=t.getYear(),o=t.getMonth()+1,r=t.getDate();return n<2e3&&(n=1900+n),o<10&&(o="0"+o),r<10&&(r="0"+r),e.opts.dateFormat.replace(/year/i,n).replace(/month/i,o).replace(/day/i,r)},exec:function(){this.insertText(mt.date._date(this))},txtExec:function(){this.insertText(mt.date._date(this))},tooltip:"Insert current date"},time:{_time:function(){var e=new Date,t=e.getHours(),n=e.getMinutes(),o=e.getSeconds();return t<10&&(t="0"+t),n<10&&(n="0"+n),o<10&&(o="0"+o),t+":"+n+":"+o},exec:function(){this.insertText(mt.time._time())},txtExec:function(){this.insertText(mt.time._time())},tooltip:"Insert current time"},ltr:{state:function(e,t){return t&&"ltr"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();this.focus(),(t&&!qe(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!qe(t,"body")))&&je(t,"direction","ltr"===je(t,"direction")?"":"ltr")},tooltip:"Left-to-Right"},rtl:{state:function(e,t){return t&&"rtl"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();this.focus(),(t&&!qe(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!qe(t,"body")))&&je(t,"direction","rtl"===je(t,"direction")?"":"rtl")},tooltip:"Right-to-Left"},print:{exec:"print",tooltip:"Print"},maximize:{state:function(){return this.maximize()},exec:function(){this.maximize(!this.maximize())},txtExec:function(){this.maximize(!this.maximize())},tooltip:"Maximize",shortcut:"Ctrl+Shift+M"},source:{state:function(){return this.sourceMode()},exec:function(){this.toggleSourceMode()},txtExec:function(){this.toggleSourceMode()},tooltip:"View source",shortcut:"Ctrl+Shift+S"},ignore:{}},k={};function gt(i){var r=this,l=[],a=function(e){return"signal"+e.charAt(0).toUpperCase()+e.slice(1)},e=function(e,t){e=[].slice.call(e);var n,o,r=a(e.shift());for(n=0;ns.length&&c&&3===c.nodeType;)o=c.nodeValue,r=n-s.length,a&&(l=o.length,i=0),a=c,t?(u=i=Math.max(l-r,0),s=o.substr(i,l-i)+s,c=a.previousSibling):(u=i+(l=Math.min(r,o.length)),s+=o.substr(i,l),c=a.nextSibling);return{node:a||c,offset:u,text:s}};function vt(i,e){var l,a,s=e||i.contentDocument||i.document,c="sceditor-start-marker",u="sceditor-end-marker",y=this;y.insertHTML=function(e,t){var n,o;if(!y.selectedRange())return!1;for(t&&(e+=y.selectedHtml()+t),o=De("p",{},s),n=s.createDocumentFragment(),o.innerHTML=e;o.firstChild;)Re(n,o.firstChild);y.insertNode(n)},a=function(e,t,n){var o,r=s.createDocumentFragment();if("string"==typeof e?(t&&(e+=y.selectedHtml()+t),r=et(e)):(Re(r,e),t&&(Re(r,y.selectedRange().extractContents()),Re(r,t))),o=r.lastChild){for(;!rt(o.lastChild,!0);)o=o.lastChild;if(ot(o)?o.lastChild||Re(o,document.createTextNode("​")):o=r,y.removeMarkers(),Re(o,l(c)),Re(o,l(u)),n){var i=De("div");return Re(i,r),i.innerHTML}return r}},y.insertNode=function(e,t){var n=a(e,t),o=y.selectedRange(),r=o.commonAncestorContainer;if(!n)return!1;o.deleteContents(),r&&3!==r.nodeType&&!ot(r)?Ve(n,r):o.insertNode(n),y.restoreRange()},y.cloneSelected=function(){var e=y.selectedRange();if(e)return e.cloneRange()},y.selectedRange=function(){var e,t,n=i.getSelection();if(n){if(n.rangeCount<=0){for(t=s.body;t.firstChild;)t=t.firstChild;(e=s.createRange()).setStartBefore(t),n.addRange(e)}return 0'})},void n.readAsDataURL(t);l[a[c]]=i.getData(a[c])}l.text=l["text/plain"],l.html=l["text/html"],P(l)}else if(!T){var u=r.scrollTop;for(v.saveRange(),T=bt.createDocumentFragment();r.firstChild;)Re(T,r.firstChild);setTimeout(function(){var e=r.innerHTML;r.innerHTML="",Re(r,T),r.scrollTop=u,T=!1,v.restoreRange(),P({html:e})},0)}},P=function(e){var t=De("div",{},d);r.call("pasteRaw",e),Qe(w,"pasteraw",e),e.html?(t.innerHTML=e.html,it(t)):t.innerHTML=pt(e.text||"");var n={val:t.innerHTML};"fragmentToSource"in l&&(n.val=l.fragmentToSource(n.val,d,y)),r.call("paste",n),Qe(w,"paste",n),"fragmentToHtml"in l&&(n.val=l.fragmentToHtml(n.val,y)),r.call("pasteHtml",n),de.wysiwygEditorInsertHtml(n.val,null,!0)},de.closeDropDown=function(e){f&&(Be(f),f=null),!0===e&&de.focus()},de.wysiwygEditorInsertHtml=function(e,t,n){var o,r,i,l=Xe(s);de.focus(),!n&&Ne(b,"code")||(v.insertHTML(e,t),v.saveRange(),N(),_e(o=He(h,"#sceditor-end-marker")[0]),r=h.scrollTop,i=at(o).top+1.5*o.offsetHeight-l,Oe(o),(r").replace(/&/g,"&")),de.wysiwygEditorInsertHtml(e),de},de.getWysiwygEditorValue=function(e){for(var t,n=De("div",{},d),o=h.childNodes,r=0;r"+(xt?"":"
    ")+"

    "),h.innerHTML=e,N(),Z(),le(),ue()},de.setSourceEditorValue=function(e){c.value=e,le()},de.updateOriginal=function(){a.value=de.val()},N=function(){var e,c,u,d,t,f,p;ye.emoticonsEnabled&&(e=h,c=ve,u=ye.emoticonsCompat,d=e.ownerDocument,t="(^|\\s| | | | |$)",f=[],p={},Me(e,"code")||(Ee(c,function(e){p[e]=new RegExp(t+ft(e)+t),f.push(e)}),f.sort(function(e,t){return t.length-e.length}),function e(t){for(t=t.firstChild;t;){if(t.nodeType!==Se||qe(t,"code")||e(t),t.nodeType===Te)for(var n=0;n")}e.preventDefault()}},Z=function(){Ze(h,function(e){if(e.nodeType===Se&&!/inline/.test(je(e,"display"))&&!qe(e,".sceditor-nlf")&&tt(e)){var t=De("p",{},d);return t.className="sceditor-nlf",t.innerHTML=wt?"":"
    ",Re(h,t),!1}if(3===e.nodeType&&!/^\s*$/.test(e.nodeValue)||qe(e,"br"))return!1})},$=function(){de.val(a.value)},U=function(){de.closeDropDown(),p=null},de._=function(){var n=arguments;return t&&t[n[0]]&&(n[0]=t[n[0]]),n[0].replace(/\{(\d+)\}/g,function(e,t){return void 0!==n[t-0+1]?n[t-0+1]:"{"+t+"}"})},K=function(t){r&&r.call(t.type+"Event",t,de);var e=(t.target===c?"scesrc":"scewys")+t.type;fe[e]&&fe[e].forEach(function(e){e.call(de,t)})},de.bind=function(e,t,n,o){for(var r=(e=e.split(" ")).length;r--;)if(we(t)){var i="scewys"+e[r],l="scesrc"+e[r];n||(fe[i]=fe[i]||[],fe[i].push(t)),o||(fe[l]=fe[l]||[],fe[l].push(t)),"valuechanged"===e[r]&&(le.hasHandler=!0)}return de},de.unbind=function(e,t,n,o){for(var r=(e=e.split(" ")).length;r--;)we(t)&&(n||Ce(fe["scewys"+e[r]]||[],t),o||Ce(fe["scesrc"+e[r]]||[],t));return de},de.blur=function(e,t,n){return we(e)?de.bind("blur",e,t,n):de.sourceMode()?c.blur():h.blur(),de},de.focus=function(e,t,n){if(we(e))de.bind("focus",e,t,n);else if(de.inSourceMode())c.focus();else{if(He(d,":focus").length)return;var o,r=v.selectedRange();x||ne(),!wt&&r&&1===r.endOffset&&r.collapsed&&(o=r.endContainer)&&1===o.childNodes.length&&qe(o.firstChild,"br")&&(r.setStartBefore(o.firstChild),r.collapse(!0),v.selectRange(r)),i.focus(),h.focus(),p&&(v.selectRange(p),p=null)}return G(),de},de.keyDown=function(e,t,n){return de.bind("keydown",e,t,n)},de.keyPress=function(e,t,n){return de.bind("keypress",e,t,n)},de.keyUp=function(e,t,n){return de.bind("keyup",e,t,n)},de.nodeChanged=function(e){return de.bind("nodechanged",e,!1,!0)},de.selectionChanged=function(e){return de.bind("selectionchanged",e,!1,!0)},de.valueChanged=function(e,t,n){return de.bind("valuechanged",e,t,n)},oe=function(e){var n=0,o=de.emoticonsCache,t=String.fromCharCode(e.which);Ne(b,"code")||(o||(o=[],Ee(ve,function(e,t){o[n++]=[e,t]}),o.sort(function(e,t){return e[0].length-t[0].length}),de.emoticonsCache=o,de.longestEmoticonCode=o[o.length-1][0].length),v.replaceKeyword(de.emoticonsCache,!0,!0,de.longestEmoticonCode,ye.emoticonsCompat,t)&&(ye.emoticonsCompat&&/^\s$/.test(t)||e.preventDefault()))},re=function(){!function(e,t){var n=/[^\s\xA0\u2002\u2003\u2009\u00a0]+/,o=e&&He(e,"img[data-sceditor-emoticon]");if(e&&o.length)for(var r=0;r","/":"?","\\":"|","[":"{","]":"}"},o={109:"-",110:"del",111:"/",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},r=e.which,i={8:"backspace",9:"tab",13:"enter",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",91:"win",92:"win",93:"select",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scrolllock",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}[r]||String.fromCharCode(r).toLowerCase();(e.ctrlKey||e.metaKey)&&t.push("ctrl"),e.altKey&&t.push("alt"),e.shiftKey&&(t.push("shift"),o[r]?i=o[r]:n[i]&&(i=n[i])),i&&(r<16||18{0}"},i:{tags:{i:null,em:null},styles:{"font-style":["italic","oblique"]},format:"[i]{0}[/i]",html:"{0}"},u:{tags:{u:null},styles:{"text-decoration":["underline"]},format:"[u]{0}[/u]",html:"{0}"},s:{tags:{s:null,strike:null},styles:{"text-decoration":["line-through"]},format:"[s]{0}[/s]",html:"{0}"},sub:{tags:{sub:null},format:"[sub]{0}[/sub]",html:"{0}"},sup:{tags:{sup:null},format:"[sup]{0}[/sup]",html:"{0}"},font:{tags:{font:{face:null}},styles:{"font-family":null},quoteType:x.never,format:function(e,t){var n;return v(e,"font")&&(n=g(e,"face"))||(n=m(e,"font-family")),"[font="+E(n)+"]"+t+"[/font]"},html:'{0}'},size:{tags:{font:{size:null}},styles:{"font-size":null},format:function(e,t){var n=g(e,"size"),o=2;return n||(n=m(e,"fontSize")),-1{!0}'},color:{tags:{font:{color:null}},styles:{color:null},quoteType:x.never,format:function(e,t){var n;return v(e,"font")&&(n=g(e,"color"))||(n=e.style.color||m(e,"color")),"[color="+u(n)+"]"+t+"[/color]"},html:function(e,t,n){return''+n+""}},ul:{tags:{ul:null},breakStart:!0,isInline:!1,skipLastLineBreak:!0,format:"[ul]{0}[/ul]",html:"
      {0}
    "},list:{breakStart:!0,isInline:!1,skipLastLineBreak:!0,html:"
      {0}
    "},ol:{tags:{ol:null},breakStart:!0,isInline:!1,skipLastLineBreak:!0,format:"[ol]{0}[/ol]",html:"
      {0}
    "},li:{tags:{li:null},isInline:!1,closedBy:["/ul","/ol","/list","*","li"],format:"[li]{0}[/li]",html:"
  • {0}
  • "},"*":{isInline:!1,closedBy:["/ul","/ol","/list","*","li"],html:"
  • {0}
  • "},table:{tags:{table:null},isInline:!1,isHtmlInline:!0,skipLastLineBreak:!0,format:"[table]{0}[/table]",html:"{0}
    "},tr:{tags:{tr:null},isInline:!1,skipLastLineBreak:!0,format:"[tr]{0}[/tr]",html:"{0}"},th:{tags:{th:null},allowsEmpty:!0,isInline:!1,format:"[th]{0}[/th]",html:"{0}"},td:{tags:{td:null},allowsEmpty:!0,isInline:!1,format:"[td]{0}[/td]",html:"{0}"},emoticon:{allowsEmpty:!0,tags:{img:{src:null,"data-sceditor-emoticon":null}},format:function(e,t){return g(e,b)+t},html:"{0}"},hr:{tags:{hr:null},allowsEmpty:!0,isSelfClosing:!0,isInline:!1,format:"[hr]{0}",html:"
    "},img:{allowsEmpty:!0,tags:{img:{src:null}},allowedChildren:["#"],quoteType:x.never,format:function(t,e){var n,o,r="",i=function(e){return t.style?t.style[e]:null};return g(t,b)?e:(n=g(t,"width")||i("width"),o=g(t,"height")||i("height"),(t.complete&&(n||o)||n&&o)&&(r="="+h.width(t)+"x"+h.height(t)),"[img"+r+"]"+g(t,"src")+"[/img]")},html:function(e,t,n){var o,r,i,l="";return o=t.width,r=t.height,t.defaultattr&&(o=(i=t.defaultattr.split(/x/i))[0],r=2===i.length?i[1]:i[0]),void 0!==o&&(l+=' width="'+f(o,!0)+'"'),void 0!==r&&(l+=' height="'+f(r,!0)+'"'),"'}},url:{allowsEmpty:!0,tags:{a:{href:null}},quoteType:x.never,format:function(e,t){var n=g(e,"href");return"mailto:"===n.substr(0,7)?'[email="'+n.substr(7)+'"]'+t+"[/email]":"[url="+n+"]"+t+"[/url]"},html:function(e,t,n){return t.defaultattr=f(t.defaultattr,!0)||n,''+n+""}},email:{quoteType:x.never,html:function(e,t,n){return''+n+""}},quote:{tags:{blockquote:null},isInline:!1,quoteType:x.never,format:function(e,t){for(var n,o="data-author",r="",i=e.children,l=0;!n&&l"+n),"
    "+n+"
    "}},code:{tags:{code:null},isInline:!1,allowedChildren:["#","#newline"],format:"[code]{0}[/code]",html:"{0}"},left:{styles:{"text-align":["left","-webkit-left","-moz-left","-khtml-left"]},isInline:!1,format:"[left]{0}[/left]",html:'
    {0}
    '},center:{styles:{"text-align":["center","-webkit-center","-moz-center","-khtml-center"]},isInline:!1,format:"[center]{0}[/center]",html:'
    {0}
    '},right:{styles:{"text-align":["right","-webkit-right","-moz-right","-khtml-right"]},isInline:!1,format:"[right]{0}[/right]",html:'
    {0}
    '},justify:{styles:{"text-align":["justify","-webkit-justify","-moz-justify","-khtml-justify"]},isInline:!1,format:"[justify]{0}[/justify]",html:'
    {0}
    '},youtube:{allowsEmpty:!0,tags:{iframe:{"data-youtube-id":null}},format:function(e,t){return(e=g(e,"data-youtube-id"))?"[youtube]"+e+"[/youtube]":t},html:''},rtl:{styles:{direction:["rtl"]},isInline:!1,format:"[rtl]{0}[/rtl]",html:'
    {0}
    '},ltr:{styles:{direction:["ltr"]},isInline:!1,format:"[ltr]{0}[/ltr]",html:'
    {0}
    '},ignore:{}};function k(e,o){return e.replace(/\{([^}]+)\}/g,function(e,t){var n=!0;return"!"===t.charAt(0)&&(n=!1,t=t.substring(1)),"0"===t&&(n=!1),void 0===o[t]?e:n?f(o[t],!0):o[t]})}function C(e){return"function"==typeof e}function E(e){return e?e.replace(/\\(.)/g,"$1").replace(/^(["'])(.*?)\1$/,"$2"):e}function S(e){var n=arguments;return e.replace(/\{(\d+)\}/g,function(e,t){return void 0!==n[t-0+1]?n[t-0+1]:"{"+t+"}"})}var T="open",D="content",M="newline",N="close";function c(e,t,n,o,r,i){var l=this;l.type=e,l.name=t,l.val=n,l.attrs=o||{},l.children=r||[],l.closing=i||null}function B(e){var h=this;function l(e,t){var n,o,r;return e===T&&(n=t.match(/\[([^\]\s=]+)(?:([^\]]+))?\]/))&&(r=i(n[1]),n[2]&&(n[2]=n[2].trim())&&(o=function(e){var t,n=/([^\s=]+)=(?:(?:(["'])((?:\\\2|[^\2])*?)\2)|((?:.(?!\s\S+=))*.))/g,o={};if("="===e.charAt(0)&&e.indexOf("=",1)<0)o.defaultattr=E(e.substr(1));else for("="===e.charAt(0)&&(e="defaultattr"+e);t=n.exec(e);)o[i(t[1])]=E(t[3])||t[4];return o}(n[2]))),e===N&&(n=t.match(/\[\/([^\[\]]+)\]/))&&(r=i(n[1])),e===M&&(r="#newline"),r&&(e!==T&&e!==N||w[r])||(e=D,r="#"),new c(e,r,t,o)}function p(e,t,n){for(var o=n.length;o--;)if(n[o].type===t&&n[o].name===e)return!0;return!1}function m(e,t){var n=(e?w[e.name]:{}).allowedChildren;return!h.opts.fixInvalidChildren||!n||-1"),C(r.html)?l=r.html.call(h,o,o.attrs,i):(o.attrs[0]=i,l=k(r.html,o.attrs))):l=o.val+i+(o.closing?o.closing.val:"");else{if(o.type===M){if(!n){d.push("
    ");continue}s||d.push("
    "),y||d.push("
    "),t.length||d.push("
    "),d.push("
    \n"),s=!1;continue}a=n,l=f(o.val,!0)}a&&!s?(d.push("
    "),s=!0):!a&&s&&(d.push("
    \n"),s=!1),d.push(l)}s&&d.push("
    \n");return d.join("")}(h.parse(e,t),!0)},h.toBBCode=function(e,t){return function e(t){var n,o,r,i,l,a,s,c,u,d,f=[];for(;0\n/g,"
    "),(r=a.firstChild)&&v(r,"div")&&l(r,!0),(r=a.lastChild)&&v(r,"div")&&l(r),a.innerHTML):s}function t(e,t,n,o){var r,i,l=(n=n||document).createElement("div"),a=n.createElement("div"),s=new B(c.opts.parserOptions);for(a.innerHTML=t,m(l,"visibility","hidden"),l.appendChild(a),n.body.appendChild(l),e&&(l.insertBefore(n.createTextNode("#"),l.firstChild),l.appendChild(n.createTextNode("#"))),o&&m(a,"whiteSpace",m(o,"whiteSpace")),i=a.getElementsByClassName("sceditor-ignore");i.length;)i[0].parentNode.removeChild(i[0]);return h.removeWhiteSpace(l),r=p(a),n.body.removeChild(l),r=s.toBBCode(r,!0),c.opts.bbcodeTrim&&(r=r.trim()),r}c.init=function(){c.opts=this.opts,c.elementToBbcode=p,s(w,function(n){var o,e=w[n].tags,t=w[n].styles;e&&s(e,function(e,t){o=!1===w[n].isInline,l[e]=l[e]||{},l[e][o]=l[e][o]||{},l[e][o][n]=t}),t&&s(t,function(e,t){o=!1===w[n].isInline,a[o]=a[o]||{},a[o][e]=a[o][e]||{},a[o][e][n]=t})}),this.commands=n(!0,{},r,this.commands),this.toBBCode=c.toSource,this.fromBBCode=c.toHtml},c.toHtml=e.bind(null,!1),c.fragmentToHtml=e.bind(null,!0),c.toSource=t.bind(null,!1),c.fragmentToSource=t.bind(null,!0)}c.prototype={clone:function(){var e=this;return new c(e.type,e.name,e.val,n({},e.attrs),[],e.closing?e.closing.clone():null)},splitAt:function(e){var t,n=this.clone(),o=this.children.indexOf(e);return-1":">"," ":"  ","\r\n":"
    ","\r":"
    ","\n":"
    "};return!1!==t&&(n['"']=""",n["'"]="'",n["`"]="`"),e=e.replace(/ {2}|\r\n|[&<>\r\n'"`]/g,function(e){return n[e]||e})}var b={html:'

    ',toolbarButton:'
    {dispName}
    ',emoticon:'{key}',fontOpt:'{font}',sizeOpt:'{size}',pastetext:'
    ',table:'
    ',image:'
    ',email:'
    ',link:'
    ',youtubeMenu:'
    ',youtube:''};function mt(e,t,n){var o=b[e];return Object.keys(t).forEach(function(e){o=o.replace(new RegExp(ft("{"+e+"}"),"g"),t[e])}),n&&(o=et(o)),o}var x=v&&v<11;function w(e){if("mozHidden"in document)for(var t,n=e.getBody();n;){if((t=n).firstChild)t=t.firstChild;else{for(;t&&!t.nextSibling;)t=t.parentNode;t&&(t=t.nextSibling)}3===n.nodeType&&/[\n\r\t]+/.test(n.nodeValue)&&(/^pre/.test(Pe(n.parentNode,"whiteSpace"))||Re(n)),n=t}}var gt={bold:{exec:"bold",tooltip:"Bold",shortcut:"Ctrl+B"},italic:{exec:"italic",tooltip:"Italic",shortcut:"Ctrl+I"},underline:{exec:"underline",tooltip:"Underline",shortcut:"Ctrl+U"},strike:{exec:"strikethrough",tooltip:"Strikethrough"},subscript:{exec:"subscript",tooltip:"Subscript"},superscript:{exec:"superscript",tooltip:"Superscript"},left:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===Pe(e,"direction"),n=Pe(e,"textAlign");return"left"===n||n===(t?"start":"end")}},exec:"justifyleft",tooltip:"Align left"},center:{exec:"justifycenter",tooltip:"Center"},right:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===Pe(e,"direction"),n=Pe(e,"textAlign");return"right"===n||n===(t?"end":"start")}},exec:"justifyright",tooltip:"Align right"},justify:{exec:"justifyfull",tooltip:"Justify"},font:{_dropDown:function(t,e,n){var o=De("div");ze(o,"click","a",function(e){n(Ve(this,"font")),t.closeDropDown(!0),e.preventDefault()}),t.opts.fonts.split(",").forEach(function(e){Fe(o,mt("fontOpt",{font:e},!0))}),t.createDropDown(e,"font-picker",o)},exec:function(e){var t=this;gt.font._dropDown(t,e,function(e){t.execCommand("fontname",e)})},tooltip:"Font Name"},size:{_dropDown:function(t,e,n){var o=De("div");ze(o,"click","a",function(e){n(Ve(this,"size")),t.closeDropDown(!0),e.preventDefault()});for(var r=1;r<=7;r++)Fe(o,mt("sizeOpt",{size:r},!0));t.createDropDown(e,"fontsize-picker",o)},exec:function(e){var t=this;gt.size._dropDown(t,e,function(e){t.execCommand("fontsize",e)})},tooltip:"Font Size"},color:{_dropDown:function(t,e,n){var o=De("div"),r="",i=gt.color;i._htmlCache||(t.opts.colors.split("|").forEach(function(e){r+='
    ',e.split(",").forEach(function(e){r+=''}),r+="
    "}),i._htmlCache=r),Fe(o,et(i._htmlCache)),ze(o,"click","a",function(e){n(Ve(this,"color")),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"color-picker",o)},exec:function(e){var t=this;gt.color._dropDown(t,e,function(e){t.execCommand("forecolor",e)})},tooltip:"Font Color"},removeformat:{exec:"removeformat",tooltip:"Remove Formatting"},cut:{exec:"cut",tooltip:"Cut",errorMessage:"Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"},copy:{exec:"copy",tooltip:"Copy",errorMessage:"Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"},paste:{exec:"paste",tooltip:"Paste",errorMessage:"Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"},pastetext:{exec:function(e){var t,n=De("div"),o=this;Fe(n,mt("pastetext",{label:o._("Paste your text inside the following box:"),insert:o._("Insert")},!0)),ze(n,"click",".button",function(e){(t=He(n,"#txt")[0].value)&&o.wysiwygEditorInsertText(t),o.closeDropDown(!0),e.preventDefault()}),o.createDropDown(e,"pastetext",n)},tooltip:"Paste Text"},bulletlist:{exec:function(){w(this),this.execCommand("insertunorderedlist")},tooltip:"Bullet list"},orderedlist:{exec:function(){w(this),this.execCommand("insertorderedlist")},tooltip:"Numbered list"},indent:{state:function(e,t){var n,o,r;return We(t,"li")?0:We(t,"ul,ol,menu")&&(o=(n=this.getRangeHelper().selectedRange()).startContainer.parentNode,r=n.endContainer.parentNode,o!==o.parentNode.firstElementChild||We(r,"li")&&r!==r.parentNode.lastElementChild)?0:-1},exec:function(){var e=this.getRangeHelper().getFirstBlockParent();this.focus(),Ne(e,"ul,ol,menu")&&this.execCommand("indent")},tooltip:"Add indent"},outdent:{state:function(e,t){return Ne(t,"ul,ol,menu")?0:-1},exec:function(){Ne(this.getRangeHelper().getFirstBlockParent(),"ul,ol,menu")&&this.execCommand("outdent")},tooltip:"Remove one indent"},table:{exec:function(e){var r=this,i=De("div");Fe(i,mt("table",{rows:r._("Rows:"),cols:r._("Cols:"),insert:r._("Insert")},!0)),ze(i,"click",".button",function(e){var t=Number(He(i,"#rows")[0].value),n=Number(He(i,"#cols")[0].value),o="";0"+Array(n+1).join("")+""),o+="
    "+(x?"":"
    ")+"
    ",r.wysiwygEditorInsertHtml(o),r.closeDropDown(!0),e.preventDefault())}),r.createDropDown(e,"inserttable",i)},tooltip:"Insert a table"},horizontalrule:{exec:"inserthorizontalrule",tooltip:"Insert a horizontal rule"},code:{exec:function(){this.wysiwygEditorInsertHtml("",(x?"":"
    ")+"
    ")},tooltip:"Code"},image:{_dropDown:function(t,e,n,o){var r=De("div");Fe(r,mt("image",{url:t._("URL:"),width:t._("Width (optional):"),height:t._("Height (optional):"),insert:t._("Insert")},!0));var i=He(r,"#image")[0];i.value=n,ze(r,"click",".button",function(e){i.value&&o(i.value,He(r,"#width")[0].value,He(r,"#height")[0].value),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"insertimage",r)},exec:function(e){var r=this;gt.image._dropDown(r,e,"",function(e,t,n){var o="";t&&(o+=' width="'+t+'"'),n&&(o+=' height="'+n+'"'),r.wysiwygEditorInsertHtml("')})},tooltip:"Insert an image"},email:{_dropDown:function(n,e,o){var r=De("div");Fe(r,mt("email",{label:n._("E-mail:"),desc:n._("Description (optional):"),insert:n._("Insert")},!0)),ze(r,"click",".button",function(e){var t=He(r,"#email")[0].value;t&&o(t,He(r,"#des")[0].value),n.closeDropDown(!0),e.preventDefault()}),n.createDropDown(e,"insertemail",r)},exec:function(e){var n=this;gt.email._dropDown(n,e,function(e,t){n.focus(),!n.getRangeHelper().selectedHtml()||t?n.wysiwygEditorInsertHtml(''+(t||e)+""):n.execCommand("createlink","mailto:"+e)})},tooltip:"Insert an email"},link:{_dropDown:function(t,e,n){var o=De("div");Fe(o,mt("link",{url:t._("URL:"),desc:t._("Description (optional):"),ins:t._("Insert")},!0));var r=He(o,"#link")[0];function i(e){r.value&&n(r.value,He(o,"#des")[0].value),t.closeDropDown(!0),e.preventDefault()}ze(o,"click",".button",i),ze(o,"keypress",function(e){13===e.which&&r.value&&i(e)},_e),t.createDropDown(e,"insertlink",o)},exec:function(e){var n=this;gt.link._dropDown(n,e,function(e,t){n.focus(),t||!n.getRangeHelper().selectedHtml()?(t=t||e,n.wysiwygEditorInsertHtml(''+t+"")):n.execCommand("createlink",e)})},tooltip:"Insert a link"},unlink:{state:function(){return Ne(this.currentNode(),"a")?0:-1},exec:function(){var e=Ne(this.currentNode(),"a");if(e){for(;e.firstChild;)je(e.firstChild,e);Re(e)}},tooltip:"Unlink"},quote:{exec:function(e,t,n){var o="
    ",r="
    ";t?(o=o+(n=n?""+n+"":"")+t+r,r=null):""===this.getRangeHelper().selectedHtml()&&(r=(x?"":"
    ")+r),this.wysiwygEditorInsertHtml(o,r)},tooltip:"Insert a Quote"},emoticon:{exec:function(f){var p=this,m=function(e){var t,n,o=p.opts,r=o.emoticonsRoot||"",i=o.emoticonsCompat,a=p.getRangeHelper(),l=i&&" "!==a.getOuterText(!0,1)?" ":"",c=i&&" "!==a.getOuterText(!1,1)?" ":"",s=De("div"),u=De("div"),d=Ce({},o.emoticons.dropdown,e?o.emoticons.more:{});return Fe(s,u),n=Math.sqrt(Object.keys(d).length),ze(s,"click","img",function(e){p.insert(l+Ae(this,"alt")+c,null,!1).closeDropDown(!0),e.preventDefault()}),Se(d,function(e,t){Fe(u,De("img",{src:r+(t.url||t),alt:e,title:t.tooltip||e})),u.children.length>=n&&(u=De("div"),Fe(s,u))}),!e&&o.emoticons.more&&(Fe(t=De("a",{className:"sceditor-more"}),document.createTextNode(p._("More"))),ze(t,"click",function(e){p.createDropDown(f,"more-emoticons",m(!0)),e.preventDefault()}),Fe(s,t)),s};p.createDropDown(f,"emoticons",m(!1))},txtExec:function(e){gt.emoticon.exec.call(this,e)},tooltip:"Insert an emoticon"},youtube:{_dropDown:function(i,e,a){var l=De("div");Fe(l,mt("youtubeMenu",{label:i._("Video URL:"),insert:i._("Insert")},!0)),ze(l,"click",".button",function(e){var t=He(l,"#link")[0].value,n=t.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/),o=t.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/),r=0;o&&Se(o[1].split(/[hms]/),function(e,t){""!==t&&(r=60*r+Number(t))}),n&&/^[a-zA-Z0-9_\-]{11}$/.test(n[1])&&a(n[1],r),i.closeDropDown(!0),e.preventDefault()}),i.createDropDown(e,"insertlink",l)},exec:function(e){var n=this;gt.youtube._dropDown(n,e,function(e,t){n.wysiwygEditorInsertHtml(mt("youtube",{id:e,time:t}))})},tooltip:"Insert a YouTube video"},date:{_date:function(e){var t=new Date,n=t.getYear(),o=t.getMonth()+1,r=t.getDate();return n<2e3&&(n=1900+n),o<10&&(o="0"+o),r<10&&(r="0"+r),e.opts.dateFormat.replace(/year/i,n).replace(/month/i,o).replace(/day/i,r)},exec:function(){this.insertText(gt.date._date(this))},txtExec:function(){this.insertText(gt.date._date(this))},tooltip:"Insert current date"},time:{_time:function(){var e=new Date,t=e.getHours(),n=e.getMinutes(),o=e.getSeconds();return t<10&&(t="0"+t),n<10&&(n="0"+n),o<10&&(o="0"+o),t+":"+n+":"+o},exec:function(){this.insertText(gt.time._time())},txtExec:function(){this.insertText(gt.time._time())},tooltip:"Insert current time"},ltr:{state:function(e,t){return t&&"ltr"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();(this.focus(),t&&!We(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!We(t,"body")))&&Pe(t,"direction","ltr"===Pe(t,"direction")?"":"ltr")},tooltip:"Left-to-Right"},rtl:{state:function(e,t){return t&&"rtl"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();(this.focus(),t&&!We(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!We(t,"body")))&&Pe(t,"direction","rtl"===Pe(t,"direction")?"":"rtl")},tooltip:"Right-to-Left"},print:{exec:"print",tooltip:"Print"},maximize:{state:function(){return this.maximize()},exec:function(){this.maximize(!this.maximize())},txtExec:function(){this.maximize(!this.maximize())},tooltip:"Maximize",shortcut:"Ctrl+Shift+M"},source:{state:function(){return this.sourceMode()},exec:function(){this.toggleSourceMode()},txtExec:function(){this.toggleSourceMode()},tooltip:"View source",shortcut:"Ctrl+Shift+S"},ignore:{}},C={};function ht(i){var r=this,a=[],l=function(e){return"signal"+e.charAt(0).toUpperCase()+e.slice(1)},e=function(e,t){e=[].slice.call(e);var n,o,r=l(e.shift());for(n=0;nc.length&&s&&3===s.nodeType;)o=s.nodeValue,r=n-c.length,l&&(a=o.length,i=0),l=s,t?(u=i=Math.max(a-r,0),c=o.substr(i,a-i)+c,s=l.previousSibling):(u=i+(a=Math.min(r,o.length)),c+=o.substr(i,a),s=l.nextSibling);return{node:l||s,offset:u,text:c}};function vt(i,e){var a,l,c=e||i.contentDocument||i.document,s="sceditor-start-marker",u="sceditor-end-marker",y=this;y.insertHTML=function(e,t){var n,o;if(!y.selectedRange())return!1;for(t&&(e+=y.selectedHtml()+t),o=De("p",{},c),n=c.createDocumentFragment(),o.innerHTML=e;o.firstChild;)Fe(n,o.firstChild);y.insertNode(n)},l=function(e,t,n){var o,r=c.createDocumentFragment();if("string"==typeof e?(t&&(e+=y.selectedHtml()+t),r=et(e)):(Fe(r,e),t&&(Fe(r,y.selectedRange().extractContents()),Fe(r,t))),o=r.lastChild){for(;!rt(o.lastChild,!0);)o=o.lastChild;if(ot(o)?o.lastChild||Fe(o,document.createTextNode("​")):o=r,y.removeMarkers(),Fe(o,a(s)),Fe(o,a(u)),n){var i=De("div");return Fe(i,r),i.innerHTML}return r}},y.insertNode=function(e,t){var n=l(e,t),o=y.selectedRange(),r=o.commonAncestorContainer;if(!n)return!1;o.deleteContents(),r&&3!==r.nodeType&&!ot(r)?je(n,r):o.insertNode(n),y.restoreRange()},y.cloneSelected=function(){var e=y.selectedRange();if(e)return e.cloneRange()},y.selectedRange=function(){var e,t,n=i.getSelection();if(n){if(n.rangeCount<=0){for(t=c.body;t.firstChild;)t=t.firstChild;(e=c.createRange()).setStartBefore(t),n.addRange(e)}return 0'})},void n.readAsDataURL(t);a[l[s]]=i.getData(l[s])}a.text=a["text/plain"],a.html=a["text/html"],V(a)}else if(!T){var u=r.scrollTop;for(v.saveRange(),T=bt.createDocumentFragment();r.firstChild;)Fe(T,r.firstChild);setTimeout(function(){var e=r.innerHTML;r.innerHTML="",Fe(r,T),r.scrollTop=u,T=!1,v.restoreRange(),V({html:e})},0)}},V=function(e){var t=De("div",{},d);r.call("pasteRaw",e),Qe(w,"pasteraw",e),e.html?(t.innerHTML=e.html,it(t)):t.innerHTML=pt(e.text||"");var n={val:t.innerHTML};"fragmentToSource"in a&&(n.val=a.fragmentToSource(n.val,d,y)),r.call("paste",n),Qe(w,"paste",n),"fragmentToHtml"in a&&(n.val=a.fragmentToHtml(n.val,y)),r.call("pasteHtml",n),de.wysiwygEditorInsertHtml(n.val,null,!0)},de.closeDropDown=function(e){f&&(Re(f),f=null),!0===e&&de.focus()},de.wysiwygEditorInsertHtml=function(e,t,n){var o,r,i,a=Xe(c);de.focus(),!n&&Ne(b,"code")||(v.insertHTML(e,t),v.saveRange(),N(),Ie(o=He(m,"#sceditor-end-marker")[0]),r=m.scrollTop,i=lt(o).top+1.5*o.offsetHeight-a,Be(o),(r").replace(/&/g,"&")),de.wysiwygEditorInsertHtml(e),de},de.getWysiwygEditorValue=function(e){for(var t,n=De("div",{},d),o=m.childNodes,r=0;r"+(xt?"":"
    ")+"

    "),m.innerHTML=e,N(),Z(),ae(),ue()},de.setSourceEditorValue=function(e){s.value=e,ae()},de.updateOriginal=function(){l.value=de.val()},N=function(){var e,s,u,d,t,f,p;ye.emoticonsEnabled&&(e=m,s=ve,u=ye.emoticonsCompat,d=e.ownerDocument,t="(^|\\s| | | | |$)",f=[],p={},Me(e,"code")||(Se(s,function(e){p[e]=new RegExp(t+ft(e)+t),f.push(e)}),f.sort(function(e,t){return t.length-e.length}),function e(t){for(t=t.firstChild;t;){if(t.nodeType!==Ee||We(t,"code")||e(t),t.nodeType===Te)for(var n=0;n")}e.preventDefault()}}},Z=function(){Ze(m,function(e){if(e.nodeType===Ee&&!/inline/.test(Pe(e,"display"))&&!We(e,".sceditor-nlf")&&tt(e)){var t=De("p",{},d);return t.className="sceditor-nlf",t.innerHTML=wt?"":"
    ",Fe(m,t),!1}if(3===e.nodeType&&!/^\s*$/.test(e.nodeValue)||We(e,"br"))return!1})},U=function(){de.val(l.value)},$=function(){de.closeDropDown(),p=null},de._=function(){var n=arguments;return t&&t[n[0]]&&(n[0]=t[n[0]]),n[0].replace(/\{(\d+)\}/g,function(e,t){return void 0!==n[t-0+1]?n[t-0+1]:"{"+t+"}"})},K=function(t){r&&r.call(t.type+"Event",t,de);var e=(t.target===s?"scesrc":"scewys")+t.type;fe[e]&&fe[e].forEach(function(e){e.call(de,t)})},de.bind=function(e,t,n,o){for(var r=(e=e.split(" ")).length;r--;)if(we(t)){var i="scewys"+e[r],a="scesrc"+e[r];n||(fe[i]=fe[i]||[],fe[i].push(t)),o||(fe[a]=fe[a]||[],fe[a].push(t)),"valuechanged"===e[r]&&(ae.hasHandler=!0)}return de},de.unbind=function(e,t,n,o){for(var r=(e=e.split(" ")).length;r--;)we(t)&&(n||ke(fe["scewys"+e[r]]||[],t),o||ke(fe["scesrc"+e[r]]||[],t));return de},de.blur=function(e,t,n){return we(e)?de.bind("blur",e,t,n):de.sourceMode()?s.blur():m.blur(),de},de.focus=function(e,t,n){if(we(e))de.bind("focus",e,t,n);else if(de.inSourceMode())s.focus();else{if(He(d,":focus").length)return;var o,r=v.selectedRange();x||ne(),!wt&&r&&1===r.endOffset&&r.collapsed&&(o=r.endContainer)&&1===o.childNodes.length&&We(o.firstChild,"br")&&(r.setStartBefore(o.firstChild),r.collapse(!0),v.selectRange(r)),i.focus(),m.focus(),p&&(v.selectRange(p),p=null)}return G(),de},de.keyDown=function(e,t,n){return de.bind("keydown",e,t,n)},de.keyPress=function(e,t,n){return de.bind("keypress",e,t,n)},de.keyUp=function(e,t,n){return de.bind("keyup",e,t,n)},de.nodeChanged=function(e){return de.bind("nodechanged",e,!1,!0)},de.selectionChanged=function(e){return de.bind("selectionchanged",e,!1,!0)},de.valueChanged=function(e,t,n){return de.bind("valuechanged",e,t,n)},oe=function(e){var n=0,o=de.emoticonsCache,t=String.fromCharCode(e.which);Ne(b,"code")||(o||(o=[],Se(ve,function(e,t){o[n++]=[e,t]}),o.sort(function(e,t){return e[0].length-t[0].length}),de.emoticonsCache=o,de.longestEmoticonCode=o[o.length-1][0].length),v.replaceKeyword(de.emoticonsCache,!0,!0,de.longestEmoticonCode,ye.emoticonsCompat,t)&&(ye.emoticonsCompat&&/^\s$/.test(t)||e.preventDefault()))},re=function(){!function(e,t){var n=/[^\s\xA0\u2002\u2003\u2009\u00a0]+/,o=e&&He(e,"img[data-sceditor-emoticon]");if(e&&o.length)for(var r=0;r","/":"?","\\":"|","[":"{","]":"}"},o={109:"-",110:"del",111:"/",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},r=e.which,i={8:"backspace",9:"tab",13:"enter",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",91:"win",92:"win",93:"select",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scrolllock",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}[r]||String.fromCharCode(r).toLowerCase();(e.ctrlKey||e.metaKey)&&t.push("ctrl"),e.altKey&&t.push("alt"),e.shiftKey&&(t.push("shift"),o[r]?i=o[r]:n[i]&&(i=n[i])),i&&(r<16||18":">"," ":"  ","\r\n":"
    ","\r":"
    ","\n":"
    "};return!1!==t&&(n['"']=""",n["'"]="'",n["`"]="`"),e.replace(/ {2}|\r\n|[&<>\r\n'"`]/g,function(e){return n[e]||e})}var y={html:'

    ',toolbarButton:'
    {dispName}
    ',emoticon:'{key}',fontOpt:'{font}',sizeOpt:'{size}',pastetext:'
    ',table:'
    ',image:'
    ',email:'
    ',link:'
    ',youtubeMenu:'
    ',youtube:''};function gt(e,t,n){var o=y[e];return Object.keys(t).forEach(function(e){o=o.replace(new RegExp(ft("{"+e+"}"),"g"),t[e])}),n&&(o=et(o)),o}var x=v&&v<11;function w(e){if("mozHidden"in document)for(var t,n=e.getBody();n;){if((t=n).firstChild)t=t.firstChild;else{for(;t&&!t.nextSibling;)t=t.parentNode;t&&(t=t.nextSibling)}3===n.nodeType&&/[\n\r\t]+/.test(n.nodeValue)&&(/^pre/.test(Pe(n.parentNode,"whiteSpace"))||Re(n)),n=t}}var mt={bold:{exec:"bold",tooltip:"Bold",shortcut:"Ctrl+B"},italic:{exec:"italic",tooltip:"Italic",shortcut:"Ctrl+I"},underline:{exec:"underline",tooltip:"Underline",shortcut:"Ctrl+U"},strike:{exec:"strikethrough",tooltip:"Strikethrough"},subscript:{exec:"subscript",tooltip:"Subscript"},superscript:{exec:"superscript",tooltip:"Superscript"},left:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===Pe(e,"direction"),n=Pe(e,"textAlign");return"left"===n||n===(t?"start":"end")}},exec:"justifyleft",tooltip:"Align left"},center:{exec:"justifycenter",tooltip:"Center"},right:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===Pe(e,"direction"),n=Pe(e,"textAlign");return"right"===n||n===(t?"end":"start")}},exec:"justifyright",tooltip:"Align right"},justify:{exec:"justifyfull",tooltip:"Justify"},font:{_dropDown:function(t,e,n){var o=De("div");ze(o,"click","a",function(e){n(Ve(this,"font")),t.closeDropDown(!0),e.preventDefault()}),t.opts.fonts.split(",").forEach(function(e){He(o,gt("fontOpt",{font:e},!0))}),t.createDropDown(e,"font-picker",o)},exec:function(e){var t=this;mt.font._dropDown(t,e,function(e){t.execCommand("fontname",e)})},tooltip:"Font Name"},size:{_dropDown:function(t,e,n){var o=De("div");ze(o,"click","a",function(e){n(Ve(this,"size")),t.closeDropDown(!0),e.preventDefault()});for(var i=1;i<=7;i++)He(o,gt("sizeOpt",{size:i},!0));t.createDropDown(e,"fontsize-picker",o)},exec:function(e){var t=this;mt.size._dropDown(t,e,function(e){t.execCommand("fontsize",e)})},tooltip:"Font Size"},color:{_dropDown:function(t,e,n){var o=De("div"),i="",r=mt.color;r._htmlCache||(t.opts.colors.split("|").forEach(function(e){i+='
    ',e.split(",").forEach(function(e){i+=''}),i+="
    "}),r._htmlCache=i),He(o,et(r._htmlCache)),ze(o,"click","a",function(e){n(Ve(this,"color")),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"color-picker",o)},exec:function(e){var t=this;mt.color._dropDown(t,e,function(e){t.execCommand("forecolor",e)})},tooltip:"Font Color"},removeformat:{exec:"removeformat",tooltip:"Remove Formatting"},cut:{exec:"cut",tooltip:"Cut",errorMessage:"Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"},copy:{exec:"copy",tooltip:"Copy",errorMessage:"Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"},paste:{exec:"paste",tooltip:"Paste",errorMessage:"Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"},pastetext:{exec:function(e){var t,n=De("div"),o=this;He(n,gt("pastetext",{label:o._("Paste your text inside the following box:"),insert:o._("Insert")},!0)),ze(n,"click",".button",function(e){(t=Fe(n,"#txt")[0].value)&&o.wysiwygEditorInsertText(t),o.closeDropDown(!0),e.preventDefault()}),o.createDropDown(e,"pastetext",n)},tooltip:"Paste Text"},bulletlist:{exec:function(){w(this),this.execCommand("insertunorderedlist")},tooltip:"Bullet list"},orderedlist:{exec:function(){w(this),this.execCommand("insertorderedlist")},tooltip:"Numbered list"},indent:{state:function(e,t){var n,o,i;return je(t,"li")?0:je(t,"ul,ol,menu")&&(o=(n=this.getRangeHelper().selectedRange()).startContainer.parentNode,i=n.endContainer.parentNode,o!==o.parentNode.firstElementChild||je(i,"li")&&i!==i.parentNode.lastElementChild)?0:-1},exec:function(){var e=this.getRangeHelper().getFirstBlockParent();this.focus(),Me(e,"ul,ol,menu")&&this.execCommand("indent")},tooltip:"Add indent"},outdent:{state:function(e,t){return Me(t,"ul,ol,menu")?0:-1},exec:function(){Me(this.getRangeHelper().getFirstBlockParent(),"ul,ol,menu")&&this.execCommand("outdent")},tooltip:"Remove one indent"},table:{exec:function(e){var i=this,r=De("div");He(r,gt("table",{rows:i._("Rows:"),cols:i._("Cols:"),insert:i._("Insert")},!0)),ze(r,"click",".button",function(e){var t=Number(Fe(r,"#rows")[0].value),n=Number(Fe(r,"#cols")[0].value),o="";0"+Array(n+1).join("")+""),o+="
    "+(x?"":"
    ")+"
    ",i.wysiwygEditorInsertHtml(o),i.closeDropDown(!0),e.preventDefault())}),i.createDropDown(e,"inserttable",r)},tooltip:"Insert a table"},horizontalrule:{exec:"inserthorizontalrule",tooltip:"Insert a horizontal rule"},code:{exec:function(){this.wysiwygEditorInsertHtml("",(x?"":"
    ")+"
    ")},tooltip:"Code"},image:{_dropDown:function(t,e,n,o){var i=De("div");He(i,gt("image",{url:t._("URL:"),width:t._("Width (optional):"),height:t._("Height (optional):"),insert:t._("Insert")},!0));var r=Fe(i,"#image")[0];r.value=n,ze(i,"click",".button",function(e){r.value&&o(r.value,Fe(i,"#width")[0].value,Fe(i,"#height")[0].value),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"insertimage",i)},exec:function(e){var i=this;mt.image._dropDown(i,e,"",function(e,t,n){var o="";t&&(o+=' width="'+t+'"'),n&&(o+=' height="'+n+'"'),i.wysiwygEditorInsertHtml("')})},tooltip:"Insert an image"},email:{_dropDown:function(n,e,o){var i=De("div");He(i,gt("email",{label:n._("E-mail:"),desc:n._("Description (optional):"),insert:n._("Insert")},!0)),ze(i,"click",".button",function(e){var t=Fe(i,"#email")[0].value;t&&o(t,Fe(i,"#des")[0].value),n.closeDropDown(!0),e.preventDefault()}),n.createDropDown(e,"insertemail",i)},exec:function(e){var n=this;mt.email._dropDown(n,e,function(e,t){n.focus(),!n.getRangeHelper().selectedHtml()||t?n.wysiwygEditorInsertHtml(''+(t||e)+""):n.execCommand("createlink","mailto:"+e)})},tooltip:"Insert an email"},link:{_dropDown:function(t,e,n){var o=De("div");He(o,gt("link",{url:t._("URL:"),desc:t._("Description (optional):"),ins:t._("Insert")},!0));var i=Fe(o,"#link")[0];function r(e){i.value&&n(i.value,Fe(o,"#des")[0].value),t.closeDropDown(!0),e.preventDefault()}ze(o,"click",".button",r),ze(o,"keypress",function(e){13===e.which&&i.value&&r(e)},_e),t.createDropDown(e,"insertlink",o)},exec:function(e){var n=this;mt.link._dropDown(n,e,function(e,t){n.focus(),t||!n.getRangeHelper().selectedHtml()?(t=t||e,n.wysiwygEditorInsertHtml(''+t+"")):n.execCommand("createlink",e)})},tooltip:"Insert a link"},unlink:{state:function(){return Me(this.currentNode(),"a")?0:-1},exec:function(){var e=Me(this.currentNode(),"a");if(e){for(;e.firstChild;)We(e.firstChild,e);Re(e)}},tooltip:"Unlink"},quote:{exec:function(e,t,n){var o="
    ",i="
    ";t?(o=o+(n=n?""+n+"":"")+t+i,i=null):""===this.getRangeHelper().selectedHtml()&&(i=(x?"":"
    ")+i),this.wysiwygEditorInsertHtml(o,i)},tooltip:"Insert a Quote"},emoticon:{exec:function(f){var p=this,g=function(e){var t,n,o=p.opts,i=o.emoticonsRoot||"",r=o.emoticonsCompat,a=p.getRangeHelper(),l=r&&" "!==a.getOuterText(!0,1)?" ":"",c=r&&" "!==a.getOuterText(!1,1)?" ":"",s=De("div"),u=De("div"),d=Ce({},o.emoticons.dropdown,e?o.emoticons.more:{});return He(s,u),n=Math.sqrt(Object.keys(d).length),ze(s,"click","img",function(e){p.insert(l+Oe(this,"alt")+c,null,!1).closeDropDown(!0),e.preventDefault()}),Se(d,function(e,t){He(u,De("img",{src:i+(t.url||t),alt:e,title:t.tooltip||e})),u.children.length>=n&&(u=De("div"),He(s,u))}),!e&&o.emoticons.more&&(He(t=De("a",{className:"sceditor-more"}),document.createTextNode(p._("More"))),ze(t,"click",function(e){p.createDropDown(f,"more-emoticons",g(!0)),e.preventDefault()}),He(s,t)),s};p.createDropDown(f,"emoticons",g(!1))},txtExec:function(e){mt.emoticon.exec.call(this,e)},tooltip:"Insert an emoticon"},youtube:{_dropDown:function(r,e,a){var l=De("div");He(l,gt("youtubeMenu",{label:r._("Video URL:"),insert:r._("Insert")},!0)),ze(l,"click",".button",function(e){var t=Fe(l,"#link")[0].value,n=t.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/),o=t.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/),i=0;o&&Se(o[1].split(/[hms]/),function(e,t){""!==t&&(i=60*i+Number(t))}),n&&/^[a-zA-Z0-9_\-]{11}$/.test(n[1])&&a(n[1],i),r.closeDropDown(!0),e.preventDefault()}),r.createDropDown(e,"insertlink",l)},exec:function(e){var n=this;mt.youtube._dropDown(n,e,function(e,t){n.wysiwygEditorInsertHtml(gt("youtube",{id:e,time:t}))})},tooltip:"Insert a YouTube video"},date:{_date:function(e){var t=new Date,n=t.getYear(),o=t.getMonth()+1,i=t.getDate();return n<2e3&&(n=1900+n),o<10&&(o="0"+o),i<10&&(i="0"+i),e.opts.dateFormat.replace(/year/i,n).replace(/month/i,o).replace(/day/i,i)},exec:function(){this.insertText(mt.date._date(this))},txtExec:function(){this.insertText(mt.date._date(this))},tooltip:"Insert current date"},time:{_time:function(){var e=new Date,t=e.getHours(),n=e.getMinutes(),o=e.getSeconds();return t<10&&(t="0"+t),n<10&&(n="0"+n),o<10&&(o="0"+o),t+":"+n+":"+o},exec:function(){this.insertText(mt.time._time())},txtExec:function(){this.insertText(mt.time._time())},tooltip:"Insert current time"},ltr:{state:function(e,t){return t&&"ltr"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();this.focus(),(t&&!je(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!je(t,"body")))&&Pe(t,"direction","ltr"===Pe(t,"direction")?"":"ltr")},tooltip:"Left-to-Right"},rtl:{state:function(e,t){return t&&"rtl"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();this.focus(),(t&&!je(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!je(t,"body")))&&Pe(t,"direction","rtl"===Pe(t,"direction")?"":"rtl")},tooltip:"Right-to-Left"},print:{exec:"print",tooltip:"Print"},maximize:{state:function(){return this.maximize()},exec:function(){this.maximize(!this.maximize())},txtExec:function(){this.maximize(!this.maximize())},tooltip:"Maximize",shortcut:"Ctrl+Shift+M"},source:{state:function(){return this.sourceMode()},exec:function(){this.toggleSourceMode()},txtExec:function(){this.toggleSourceMode()},tooltip:"View source",shortcut:"Ctrl+Shift+S"},ignore:{}},C={};function ht(r){var i=this,a=[],l=function(e){return"signal"+e.charAt(0).toUpperCase()+e.slice(1)},e=function(e,t){e=[].slice.call(e);var n,o,i=l(e.shift());for(n=0;nc.length&&s&&3===s.nodeType;)o=s.nodeValue,i=n-c.length,l&&(a=o.length,r=0),l=s,t?(u=r=Math.max(a-i,0),c=o.substr(r,a-r)+c,s=l.previousSibling):(u=r+(a=Math.min(i,o.length)),c+=o.substr(r,a),s=l.nextSibling);return{node:l||s,offset:u,text:c}};function vt(r,e){var a,l,c=e||r.contentDocument||r.document,s="sceditor-start-marker",u="sceditor-end-marker",b=this;b.insertHTML=function(e,t){var n,o;if(!b.selectedRange())return!1;for(t&&(e+=b.selectedHtml()+t),o=De("p",{},c),n=c.createDocumentFragment(),o.innerHTML=e;o.firstChild;)He(n,o.firstChild);b.insertNode(n)},l=function(e,t,n){var o,i=c.createDocumentFragment();if("string"==typeof e?(t&&(e+=b.selectedHtml()+t),i=et(e)):(He(i,e),t&&(He(i,b.selectedRange().extractContents()),He(i,t))),o=i.lastChild){for(;!it(o.lastChild,!0);)o=o.lastChild;if(ot(o)?o.lastChild||He(o,document.createTextNode("​")):o=i,b.removeMarkers(),He(o,a(s)),He(o,a(u)),n){var r=De("div");return He(r,i),r.innerHTML}return i}},b.insertNode=function(e,t){var n=l(e,t),o=b.selectedRange(),i=o.commonAncestorContainer;if(!n)return!1;o.deleteContents(),i&&3!==i.nodeType&&!ot(i)?We(n,i):o.insertNode(n),b.restoreRange()},b.cloneSelected=function(){var e=b.selectedRange();if(e)return e.cloneRange()},b.selectedRange=function(){var e,t,n=r.getSelection();if(n){if(n.rangeCount<=0){for(t=c.body;t.firstChild;)t=t.firstChild;(e=c.createRange()).setStartBefore(t),n.addRange(e)}return 0'})},void n.readAsDataURL(t);a[l[s]]=r.getData(l[s])}a.text=a["text/plain"],a.html=a["text/html"],V(a)}else if(!T){var u=i.scrollTop;for(v.saveRange(),T=yt.createDocumentFragment();i.firstChild;)He(T,i.firstChild);setTimeout(function(){var e=i.innerHTML;i.innerHTML="",He(i,T),i.scrollTop=u,T=!1,v.restoreRange(),V({html:e})},0)}},V=function(e){var t=De("div",{},d);i.call("pasteRaw",e),Qe(w,"pasteraw",e),e.html?(t.innerHTML=e.html,rt(t)):t.innerHTML=pt(e.text||"");var n={val:t.innerHTML};"fragmentToSource"in a&&(n.val=a.fragmentToSource(n.val,d,b)),i.call("paste",n),Qe(w,"paste",n),"fragmentToHtml"in a&&(n.val=a.fragmentToHtml(n.val,b)),i.call("pasteHtml",n),de.wysiwygEditorInsertHtml(n.val,null,!0)},de.closeDropDown=function(e){f&&(Re(f),f=null),!0===e&&de.focus()},de.wysiwygEditorInsertHtml=function(e,t,n){var o,i,r,a=Xe(c);de.focus(),!n&&Me(y,"code")||(v.insertHTML(e,t),v.saveRange(),M(),Le(o=Fe(g,"#sceditor-end-marker")[0]),i=g.scrollTop,r=lt(o).top+1.5*o.offsetHeight-a,Be(o),(i").replace(/&/g,"&")),de.wysiwygEditorInsertHtml(e),de},de.getWysiwygEditorValue=function(e){for(var t,n=De("div",{},d),o=g.childNodes,i=0;i"+(xt?"":"
    ")+"

    "),g.innerHTML=e,M(),Z(),ae(),ue()},de.setSourceEditorValue=function(e){s.value=e,ae()},de.updateOriginal=function(){l.value=de.val()},M=function(){var e,s,u,d,t,f,p;be.emoticonsEnabled&&(e=g,s=ve,u=be.emoticonsCompat,d=e.ownerDocument,t="(^|\\s| | | | |$)",f=[],p={},Ne(e,"code")||(Se(s,function(e){p[e]=new RegExp(t+ft(e)+t),f.push(e)}),f.sort(function(e,t){return t.length-e.length}),function e(t){for(t=t.firstChild;t;){if(t.nodeType!==ke||je(t,"code")||e(t),t.nodeType===Te)for(var n=0;n")}e.preventDefault()}},Z=function(){Ze(g,function(e){if(e.nodeType===ke&&!/inline/.test(Pe(e,"display"))&&!je(e,".sceditor-nlf")&&tt(e)){var t=De("p",{},d);return t.className="sceditor-nlf",t.innerHTML=wt?"":"
    ",He(g,t),!1}if(3===e.nodeType&&!/^\s*$/.test(e.nodeValue)||je(e,"br"))return!1})},U=function(){de.val(l.value)},$=function(){de.closeDropDown(),p=null},de._=function(){var n=arguments;return t&&t[n[0]]&&(n[0]=t[n[0]]),n[0].replace(/\{(\d+)\}/g,function(e,t){return void 0!==n[t-0+1]?n[t-0+1]:"{"+t+"}"})},K=function(t){i&&i.call(t.type+"Event",t,de);var e=(t.target===s?"scesrc":"scewys")+t.type;fe[e]&&fe[e].forEach(function(e){e.call(de,t)})},de.bind=function(e,t,n,o){for(var i=(e=e.split(" ")).length;i--;)if(we(t)){var r="scewys"+e[i],a="scesrc"+e[i];n||(fe[r]=fe[r]||[],fe[r].push(t)),o||(fe[a]=fe[a]||[],fe[a].push(t)),"valuechanged"===e[i]&&(ae.hasHandler=!0)}return de},de.unbind=function(e,t,n,o){for(var i=(e=e.split(" ")).length;i--;)we(t)&&(n||Ee(fe["scewys"+e[i]]||[],t),o||Ee(fe["scesrc"+e[i]]||[],t));return de},de.blur=function(e,t,n){return we(e)?de.bind("blur",e,t,n):de.sourceMode()?s.blur():g.blur(),de},de.focus=function(e,t,n){if(we(e))de.bind("focus",e,t,n);else if(de.inSourceMode())s.focus();else{if(Fe(d,":focus").length)return;var o,i=v.selectedRange();x||ne(),!wt&&i&&1===i.endOffset&&i.collapsed&&(o=i.endContainer)&&1===o.childNodes.length&&je(o.firstChild,"br")&&(i.setStartBefore(o.firstChild),i.collapse(!0),v.selectRange(i)),r.focus(),g.focus(),p&&(v.selectRange(p),p=null)}return G(),de},de.keyDown=function(e,t,n){return de.bind("keydown",e,t,n)},de.keyPress=function(e,t,n){return de.bind("keypress",e,t,n)},de.keyUp=function(e,t,n){return de.bind("keyup",e,t,n)},de.nodeChanged=function(e){return de.bind("nodechanged",e,!1,!0)},de.selectionChanged=function(e){return de.bind("selectionchanged",e,!1,!0)},de.valueChanged=function(e,t,n){return de.bind("valuechanged",e,t,n)},oe=function(e){var n=0,o=de.emoticonsCache,t=String.fromCharCode(e.which);Me(y,"code")||(o||(o=[],Se(ve,function(e,t){o[n++]=[e,t]}),o.sort(function(e,t){return e[0].length-t[0].length}),de.emoticonsCache=o,de.longestEmoticonCode=o[o.length-1][0].length),v.replaceKeyword(de.emoticonsCache,!0,!0,de.longestEmoticonCode,be.emoticonsCompat,t)&&(be.emoticonsCompat&&/^\s$/.test(t)||e.preventDefault()))},ie=function(){!function(e,t){var n=/[^\s\xA0\u2002\u2003\u2009\u00a0]+/,o=e&&Fe(e,"img[data-sceditor-emoticon]");if(e&&o.length)for(var i=0;i","/":"?","\\":"|","[":"{","]":"}"},o={109:"-",110:"del",111:"/",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},i=e.which,r={8:"backspace",9:"tab",13:"enter",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",91:"win",92:"win",93:"select",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scrolllock",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}[i]||String.fromCharCode(i).toLowerCase();(e.ctrlKey||e.metaKey)&&t.push("ctrl"),e.altKey&&t.push("alt"),e.shiftKey&&(t.push("shift"),o[i]?r=o[i]:n[r]&&(r=n[r])),r&&(i<16||18",""]},italic:{txtExec:["",""]},underline:{txtExec:['',""]},strike:{txtExec:['',""]},subscript:{txtExec:["",""]},superscript:{txtExec:["",""]},left:{txtExec:['
    ',"
    "]},center:{txtExec:['
    ',"
    "]},right:{txtExec:['
    ',"
    "]},justify:{txtExec:['
    ',"
    "]},font:{txtExec:function(e){var t=this;l("font")._dropDown(t,e,function(e){t.insertText('',"")})}},size:{txtExec:function(e){var t=this;l("size")._dropDown(t,e,function(e){t.insertText('',"")})}},color:{txtExec:function(e){var t=this;l("color")._dropDown(t,e,function(e){t.insertText('',"")})}},bulletlist:{txtExec:["
    • ","
    "]},orderedlist:{txtExec:["
    1. ","
    "]},table:{txtExec:["
    ","
    "]},horizontalrule:{txtExec:["
    "]},code:{txtExec:["",""]},image:{txtExec:function(e,t){var i=this;l("image")._dropDown(i,e,t,function(e,t,n){var o="";t&&(o+=' width="'+t+'"'),n&&(o+=' height="'+n+'"'),i.insertText("')})}},email:{txtExec:function(e,n){var o=this;l("email")._dropDown(o,e,function(e,t){o.insertText(''+(t||n||e)+"")})}},link:{txtExec:function(e,n){var o=this;l("link")._dropDown(o,e,function(e,t){o.insertText(''+(t||n||e)+"")})}},quote:{txtExec:["
    ","
    "]},youtube:{txtExec:function(e){var n=this;l("youtube")._dropDown(n,e,function(e,t){n.insertText('')})}},rtl:{txtExec:['
    ',"
    "]},ltr:{txtExec:['
    ',"
    "]}};function N(){var i=this,n={},b={};function e(e,t,n){var o,i,m,r,a,l,c,s,u,d,f,p,g,h,v=n.createElement("div");return v.innerHTML=t,S(v,"visibility","hidden"),n.body.appendChild(v),i=v,E.traverse(i,function(e){var t=e.nodeName.toLowerCase();y("*",e),y(t,e)},!0),m=v,E.traverse(m,function(e){var t,n=e.nodeName.toLowerCase(),o=e.parentNode,i=e.nodeType,r=!E.isInline(e),a=e.previousSibling,l=e.nextSibling,c=o===m,s=!a&&!l,u="iframe"!==n&&function e(t,n){var o,i=t.childNodes,r=t.nodeName.toLowerCase(),a=t.nodeValue,l=i.length,c=N.allowedEmptyTags||[];if(n&&"br"===r)return!0;if(k(t,".sceditor-ignore"))return!0;if(-1":">",'"':"""," ":" "};return e?e.replace(/[&<>"\xa0]/g,function(e){return t[e]||e}):""}function p(e,t){switch(e.nodeType){case 1:"!"===e.nodeName.toLowerCase()?n(e):function(e,t){var n,o,i,r=e.nodeName.toLowerCase(),a="iframe"===r,l=e.attributes.length,c=e.firstChild,s=t||/pre(?:\-wrap)?$/i.test(S(e,"whiteSpace")),u=!e.firstChild&&!E.canHaveChildren(e)&&!a;if(k(e,".sceditor-ignore"))return;g("<"+r,!t&&m(e));for(;l--;)o=e.attributes[l],i=o.value,g(" "+o.name.toLowerCase()+'="'+f(i)+'"',!1);g(u?" />":">",!1),a||(n=c);for(;n;)d++,p(n,s),n=n.nextSibling,d--;u||g("",!s&&!a&&m(e)&&c&&m(c))}(e,t);break;case 3:!function(e,t){var n=e.nodeValue;t||(n=n.replace(/[\r\n]/," ").replace(/[^\S|\u00A0]+/g," "));n&&g(f(n),!t&&m(e))}(e,t);break;case 4:g("");break;case 8:n(e);break;case 9:case 11:!function(e){var t=e.firstChild;for(;t;)p(t),t=t.nextSibling}(e)}}function n(e){g("\x3c!-- "+f(e.nodeValue)+" --\x3e")}function g(e,t){var n=d;if(!1!==t)for(i.length&&i.push("\n");n--;)i.push(o.indentStr);i.push(e)}function m(e){var t=e.previousSibling;return 1!==e.nodeType&&t?!E.isInline(t):!t&&!E.isInline(e.parentNode)||!E.isInline(e)}this.serialize=function(e,t){if(i=[],t)for(e=e.firstChild;e;)p(e),e=e.nextSibling;else p(e);return i.join("")}},N.converters=[{tags:{"*":{width:null}},conv:function(e){S(e,"width",n(e,"width")),T(e,"width")}},{tags:{"*":{height:null}},conv:function(e){S(e,"height",n(e,"height")),T(e,"height")}},{tags:{li:{value:null}},conv:function(e){T(e,"value")}},{tags:{"*":{text:null}},conv:function(e){S(e,"color",n(e,"text")),T(e,"text")}},{tags:{"*":{color:null}},conv:function(e){S(e,"color",n(e,"color")),T(e,"color")}},{tags:{"*":{face:null}},conv:function(e){S(e,"fontFamily",n(e,"face")),T(e,"face")}},{tags:{"*":{align:null}},conv:function(e){S(e,"textAlign",n(e,"align")),T(e,"align")}},{tags:{"*":{border:null}},conv:function(e){S(e,"borderWidth",n(e,"border")),T(e,"border")}},{tags:{applet:{name:null},img:{name:null},layer:{name:null},map:{name:null},object:{name:null},param:{name:null}},conv:function(e){n(e,"id")||n(e,"id",n(e,"name")),T(e,"name")}},{tags:{"*":{vspace:null}},conv:function(e){S(e,"marginTop",n(e,"vspace")-0),S(e,"marginBottom",n(e,"vspace")-0),T(e,"vspace")}},{tags:{"*":{hspace:null}},conv:function(e){S(e,"marginLeft",n(e,"hspace")-0),S(e,"marginRight",n(e,"hspace")-0),T(e,"hspace")}},{tags:{hr:{noshade:null}},conv:function(e){S(e,"borderStyle","solid"),T(e,"noshade")}},{tags:{"*":{nowrap:null}},conv:function(e){S(e,"whiteSpace","nowrap"),T(e,"nowrap")}},{tags:{big:null},conv:function(e){S(o(e,"span"),"fontSize","larger")}},{tags:{small:null},conv:function(e){S(o(e,"span"),"fontSize","smaller")}},{tags:{b:null},conv:function(e){o(e,"strong")}},{tags:{u:null},conv:function(e){S(o(e,"span"),"textDecoration","underline")}},{tags:{s:null,strike:null},conv:function(e){S(o(e,"span"),"textDecoration","line-through")}},{tags:{dir:null},conv:function(e){o(e,"ul")}},{tags:{center:null},conv:function(e){S(o(e,"div"),"textAlign","center")}},{tags:{font:{size:null}},conv:function(e){S(e,"fontSize",S(e,"fontSize")),T(e,"size")}},{tags:{font:null},conv:function(e){o(e,"span")}},{tags:{"*":{type:["_moz"]}},conv:function(e){T(e,"type")}},{tags:{"*":{_moz_dirty:null}},conv:function(e){T(e,"_moz_dirty")}},{tags:{"*":{_moz_editor_bogus_node:null}},conv:function(e){e.parentNode.removeChild(e)}}],N.allowedAttribs={},N.disallowedAttribs={},N.allowedTags=[],N.disallowedTags=[],N.allowedEmptyTags=[],w.formats.xhtml=N}(sceditor); \ No newline at end of file diff --git a/public/assets/minified/plugins/autosave.js b/public/assets/minified/plugins/autosave.js new file mode 100644 index 0000000..d29ee5a --- /dev/null +++ b/public/assets/minified/plugins/autosave.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(e){"use strict";var t="sce-autodraft-"+location.pathname+location.search;function i(e){localStorage.removeItem(e||t)}e.plugins.autosave=function(){var a,e=this,o=t,r=864e5,n=function(e){localStorage.setItem(o,JSON.stringify(e))},s=function(){return JSON.parse(localStorage.getItem(o))};e.init=function(){var e=(a=this).opts&&a.opts.autosave||{};n=e.save||n,s=e.load||s,o=e.storageKey||o,r=e.expires||r,function(){for(var e=0;e'),n),n.nodeValue=s[3]+i.substr(s.index+s[0].length))}else a.is(n,"code")||e(n);n=n.nextSibling}}(t),e.html=t.innerHTML}}}}(document,sceditor); \ No newline at end of file diff --git a/public/assets/minified/plugins/dragdrop.js b/public/assets/minified/plugins/dragdrop.js new file mode 100644 index 0000000..8459ab7 --- /dev/null +++ b/public/assets/minified/plugins/dragdrop.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(n){"use strict";var v="data:image/gif;base64,R0lGODlhlgBkAPABAH19ffb29iH5BAAKAAAAIf4aQ3JlYXRlZCB3aXRoIGFqYXhsb2FkLmluZm8AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAlgBkAAAC1YyPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwD2fP6iD/gH6Pc2GIhg2JeQSNjGuLf4GMlYKIloefAIUEl52ZmJyaY5mUhqyFnqmQr6KRoaMKp66hbLumpQ69oK+5qrOyg4a6qYV2x8jJysvMzc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fL99UAAAh+QQACgAAACwAAAAAlgBkAIEAAAB9fX329vYAAAAC3JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwD2fP4iABgY+CcoCNeHuJdQyLjIaOiWiOj4CEhZ+SbZd/nI2RipqYhQOThKGpAZCuBZyArZprpqSupaCqtaazmLCRqai7rb2av5W5wqSShcm8fc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5/vVAAAIfkEAAoAAAAsAAAAAJYAZACBAAAAfX199vb2AAAAAuCUj6nL7Q+jnLTai7PevPsPhuJIluaJpurKtu4Lx/JM1/aN5/rO9/4PDAqHxKLxiEwql8ym8wmNSqfUqvWKzWq33K73Cw6Lx+Sy+YxOq9fstvsNj8vn9Lr9jr8E9nz+AgAYGLjQVwhXiJgguAiYgGjo9tinyCjoKLn3hpmJUGmJsBmguUnpCXCJOZraaXoKShoJe9DqehCqKlnqiZobuzrbyvuIO8xqKpxIPKlwrPCbBx0tPU1dbX2Nna29zd3t/Q0eLj5OXm5+jp6uvs7e7v4OHy8/T19vf4+fr7/P379UAAAh+QQACgAAACwAAAAAlgBkAIEAAAB9fX329vYAAAAC4JSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2OvwT2fP6iD7gAMEhICAeImIAYiFDoOPi22KcouZfw6BhZGUBZeYlp6LbJiTD6CQqg6Vm6eQqqKtkZ24iaKtrKunpQa9tmmju7Wwu7KFtMi3oYDMzompkHHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f31QAADs=",c=void 0!==window.FileReader,u=/data:[^;]+;base64,/i;function g(e){for(var t=e.substr(5,e.indexOf(";")-5),n=atob(e.substr(e.indexOf(",")+1)),r=new Uint8Array(n.length),i=0;i
    ").firstChild),i.addEventListener("dragover",e),i.addEventListener("dragleave",d),i.addEventListener("dragend",d),i.addEventListener("drop",t),r.getBody().addEventListener("dragover",e),r.getBody().addEventListener("drop",d)},this.signalPasteHtml=function(e){if(!("handlePaste"in A)||A.handlePaste){var t=document.createElement("div");t.innerHTML=e.val;for(var n=t.querySelectorAll("img"),r=0;r",""):e.execCommand("formatblock","<"+t+">")},a=function(e){var o=this,r=document.createElement("div");i.utils.each(c,function(t,a){var e=document.createElement("a");e.className="sceditor-option",e.textContent=a.name||a,e.addEventListener("click",function(e){o.closeDropDown(!0),a.exec?a.exec(o):n(o,t),e.preventDefault()}),r.appendChild(e)}),o.createDropDown(e,"format",r)}}}(sceditor); \ No newline at end of file diff --git a/public/assets/minified/plugins/plaintext.js b/public/assets/minified/plugins/plaintext.js new file mode 100644 index 0000000..9ba5bf2 --- /dev/null +++ b/public/assets/minified/plugins/plaintext.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(t){"use strict";var i=t.utils.extend;t.plugins.plaintext=function(){var e=!0;this.init=function(){var t=this.commands,n=this.opts;n&&n.plaintext&&n.plaintext.addButton&&(e=n.plaintext.enabled,t.pastetext=i(t.pastetext||{},{state:function(){return e?1:0},exec:function(){e=!e}}))},this.signalPasteRaw=function(t){if(e){if(t.html&&!t.text){var n=document.createElement("div");n.innerHTML=t.html,t.text=n.innerText}t.html=null}}}}(sceditor); \ No newline at end of file diff --git a/public/assets/minified/plugins/strictbbcode.js b/public/assets/minified/plugins/strictbbcode.js new file mode 100644 index 0000000..8367926 --- /dev/null +++ b/public/assets/minified/plugins/strictbbcode.js @@ -0,0 +1,2 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + diff --git a/public/assets/minified/plugins/undo.js b/public/assets/minified/plugins/undo.js new file mode 100644 index 0000000..7407178 --- /dev/null +++ b/public/assets/minified/plugins/undo.js @@ -0,0 +1,3 @@ +/* SCEditor v2.1.3 | (C) 2017, Sam Clarke | sceditor.com/license */ + +!function(e){"use strict";sceditor.plugins.undo=function(){var r,o,e=this,u=0,a=50,n=[],c=[],s=!1,l=function(e){s=!0,o=e.value,r.sourceMode(e.sourceMode),r.val(e.value,!1),r.focus(),e.sourceMode?r.sourceEditorCaret(e.caret):r.getRangeHelper().restoreRange(),s=!1};e.init=function(){a=(r=this).undoLimit||a,r.addShortcut("ctrl+z",e.undo),r.addShortcut("ctrl+shift+z",e.redo),r.addShortcut("ctrl+y",e.redo)},e.undo=function(){var e=c.pop(),t=r.val(null,!1);return e&&!n.length&&t===e.value&&(e=c.pop()),e&&(n.length||n.push({caret:r.sourceEditorCaret(),sourceMode:r.sourceMode(),value:t}),n.push(e),l(e)),!1},e.redo=function(){var e=n.pop();return c.length||(c.push(e),e=n.pop()),e&&(c.push(e),l(e)),!1},e.signalReady=function(){var e=r.val(null,!1);o=e,c.push({caret:this.sourceEditorCaret(),sourceMode:this.sourceMode(),value:e})},e.signalValuechangedEvent=function(e){var t=e.detail.rawValue;0a&&c.shift(),!s&&o&&o!==t&&(n.length=0,(u+=function(e,t){var r,o,u,a,n=e.length,c=t.length,s=Math.max(n,c);for(r=0;r":">"," ":"  ","\r\n":"
    ","\r":"
    ","\n":"
    "};return!1!==t&&(n['"']=""",n["'"]="'",n["`"]="`"),e=e.replace(/ {2}|\r\n|[&<>\r\n'"`]/g,function(e){return n[e]||e})}var y={html:'

    ',toolbarButton:'
    {dispName}
    ',emoticon:'{key}',fontOpt:'{font}',sizeOpt:'{size}',pastetext:'
    ',table:'
    ',image:'
    ',email:'
    ',link:'
    ',youtubeMenu:'
    ',youtube:''};function mt(e,t,n){var o=y[e];return Object.keys(t).forEach(function(e){o=o.replace(new RegExp(ft("{"+e+"}"),"g"),t[e])}),n&&(o=et(o)),o}var b=h&&h<11;function x(e){if("mozHidden"in document)for(var t,n=e.getBody();n;){if((t=n).firstChild)t=t.firstChild;else{for(;t&&!t.nextSibling;)t=t.parentNode;t&&(t=t.nextSibling)}3===n.nodeType&&/[\n\r\t]+/.test(n.nodeValue)&&(/^pre/.test(Pe(n.parentNode,"whiteSpace"))||Re(n)),n=t}}var gt={bold:{exec:"bold",tooltip:"Bold",shortcut:"Ctrl+B"},italic:{exec:"italic",tooltip:"Italic",shortcut:"Ctrl+I"},underline:{exec:"underline",tooltip:"Underline",shortcut:"Ctrl+U"},strike:{exec:"strikethrough",tooltip:"Strikethrough"},subscript:{exec:"subscript",tooltip:"Subscript"},superscript:{exec:"superscript",tooltip:"Superscript"},left:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===Pe(e,"direction"),n=Pe(e,"textAlign");return"left"===n||n===(t?"start":"end")}},exec:"justifyleft",tooltip:"Align left"},center:{exec:"justifycenter",tooltip:"Center"},right:{state:function(e){if(e&&3===e.nodeType&&(e=e.parentNode),e){var t="ltr"===Pe(e,"direction"),n=Pe(e,"textAlign");return"right"===n||n===(t?"end":"start")}},exec:"justifyright",tooltip:"Align right"},justify:{exec:"justifyfull",tooltip:"Justify"},font:{_dropDown:function(t,e,n){var o=De("div");ze(o,"click","a",function(e){n(Ve(this,"font")),t.closeDropDown(!0),e.preventDefault()}),t.opts.fonts.split(",").forEach(function(e){Fe(o,mt("fontOpt",{font:e},!0))}),t.createDropDown(e,"font-picker",o)},exec:function(e){var t=this;gt.font._dropDown(t,e,function(e){t.execCommand("fontname",e)})},tooltip:"Font Name"},size:{_dropDown:function(t,e,n){var o=De("div");ze(o,"click","a",function(e){n(Ve(this,"size")),t.closeDropDown(!0),e.preventDefault()});for(var r=1;r<=7;r++)Fe(o,mt("sizeOpt",{size:r},!0));t.createDropDown(e,"fontsize-picker",o)},exec:function(e){var t=this;gt.size._dropDown(t,e,function(e){t.execCommand("fontsize",e)})},tooltip:"Font Size"},color:{_dropDown:function(t,e,n){var o=De("div"),r="",i=gt.color;i._htmlCache||(t.opts.colors.split("|").forEach(function(e){r+='
    ',e.split(",").forEach(function(e){r+=''}),r+="
    "}),i._htmlCache=r),Fe(o,et(i._htmlCache)),ze(o,"click","a",function(e){n(Ve(this,"color")),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"color-picker",o)},exec:function(e){var t=this;gt.color._dropDown(t,e,function(e){t.execCommand("forecolor",e)})},tooltip:"Font Color"},removeformat:{exec:"removeformat",tooltip:"Remove Formatting"},cut:{exec:"cut",tooltip:"Cut",errorMessage:"Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"},copy:{exec:"copy",tooltip:"Copy",errorMessage:"Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"},paste:{exec:"paste",tooltip:"Paste",errorMessage:"Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"},pastetext:{exec:function(e){var t,n=De("div"),o=this;Fe(n,mt("pastetext",{label:o._("Paste your text inside the following box:"),insert:o._("Insert")},!0)),ze(n,"click",".button",function(e){(t=He(n,"#txt")[0].value)&&o.wysiwygEditorInsertText(t),o.closeDropDown(!0),e.preventDefault()}),o.createDropDown(e,"pastetext",n)},tooltip:"Paste Text"},bulletlist:{exec:function(){x(this),this.execCommand("insertunorderedlist")},tooltip:"Bullet list"},orderedlist:{exec:function(){x(this),this.execCommand("insertorderedlist")},tooltip:"Numbered list"},indent:{state:function(e,t){var n,o,r;return We(t,"li")?0:We(t,"ul,ol,menu")&&(o=(n=this.getRangeHelper().selectedRange()).startContainer.parentNode,r=n.endContainer.parentNode,o!==o.parentNode.firstElementChild||We(r,"li")&&r!==r.parentNode.lastElementChild)?0:-1},exec:function(){var e=this.getRangeHelper().getFirstBlockParent();this.focus(),Ne(e,"ul,ol,menu")&&this.execCommand("indent")},tooltip:"Add indent"},outdent:{state:function(e,t){return Ne(t,"ul,ol,menu")?0:-1},exec:function(){Ne(this.getRangeHelper().getFirstBlockParent(),"ul,ol,menu")&&this.execCommand("outdent")},tooltip:"Remove one indent"},table:{exec:function(e){var r=this,i=De("div");Fe(i,mt("table",{rows:r._("Rows:"),cols:r._("Cols:"),insert:r._("Insert")},!0)),ze(i,"click",".button",function(e){var t=Number(He(i,"#rows")[0].value),n=Number(He(i,"#cols")[0].value),o="";0"+Array(n+1).join("")+""),o+="
    "+(b?"":"
    ")+"
    ",r.wysiwygEditorInsertHtml(o),r.closeDropDown(!0),e.preventDefault())}),r.createDropDown(e,"inserttable",i)},tooltip:"Insert a table"},horizontalrule:{exec:"inserthorizontalrule",tooltip:"Insert a horizontal rule"},code:{exec:function(){this.wysiwygEditorInsertHtml("",(b?"":"
    ")+"
    ")},tooltip:"Code"},image:{_dropDown:function(t,e,n,o){var r=De("div");Fe(r,mt("image",{url:t._("URL:"),width:t._("Width (optional):"),height:t._("Height (optional):"),insert:t._("Insert")},!0));var i=He(r,"#image")[0];i.value=n,ze(r,"click",".button",function(e){i.value&&o(i.value,He(r,"#width")[0].value,He(r,"#height")[0].value),t.closeDropDown(!0),e.preventDefault()}),t.createDropDown(e,"insertimage",r)},exec:function(e){var r=this;gt.image._dropDown(r,e,"",function(e,t,n){var o="";t&&(o+=' width="'+t+'"'),n&&(o+=' height="'+n+'"'),r.wysiwygEditorInsertHtml("')})},tooltip:"Insert an image"},email:{_dropDown:function(n,e,o){var r=De("div");Fe(r,mt("email",{label:n._("E-mail:"),desc:n._("Description (optional):"),insert:n._("Insert")},!0)),ze(r,"click",".button",function(e){var t=He(r,"#email")[0].value;t&&o(t,He(r,"#des")[0].value),n.closeDropDown(!0),e.preventDefault()}),n.createDropDown(e,"insertemail",r)},exec:function(e){var n=this;gt.email._dropDown(n,e,function(e,t){n.focus(),!n.getRangeHelper().selectedHtml()||t?n.wysiwygEditorInsertHtml(''+(t||e)+""):n.execCommand("createlink","mailto:"+e)})},tooltip:"Insert an email"},link:{_dropDown:function(t,e,n){var o=De("div");Fe(o,mt("link",{url:t._("URL:"),desc:t._("Description (optional):"),ins:t._("Insert")},!0));var r=He(o,"#link")[0];function i(e){r.value&&n(r.value,He(o,"#des")[0].value),t.closeDropDown(!0),e.preventDefault()}ze(o,"click",".button",i),ze(o,"keypress",function(e){13===e.which&&r.value&&i(e)},_e),t.createDropDown(e,"insertlink",o)},exec:function(e){var n=this;gt.link._dropDown(n,e,function(e,t){n.focus(),t||!n.getRangeHelper().selectedHtml()?(t=t||e,n.wysiwygEditorInsertHtml(''+t+"")):n.execCommand("createlink",e)})},tooltip:"Insert a link"},unlink:{state:function(){return Ne(this.currentNode(),"a")?0:-1},exec:function(){var e=Ne(this.currentNode(),"a");if(e){for(;e.firstChild;)je(e.firstChild,e);Re(e)}},tooltip:"Unlink"},quote:{exec:function(e,t,n){var o="
    ",r="
    ";t?(o=o+(n=n?""+n+"":"")+t+r,r=null):""===this.getRangeHelper().selectedHtml()&&(r=(b?"":"
    ")+r),this.wysiwygEditorInsertHtml(o,r)},tooltip:"Insert a Quote"},emoticon:{exec:function(f){var p=this,m=function(e){var t,n,o=p.opts,r=o.emoticonsRoot||"",i=o.emoticonsCompat,a=p.getRangeHelper(),l=i&&" "!==a.getOuterText(!0,1)?" ":"",c=i&&" "!==a.getOuterText(!1,1)?" ":"",s=De("div"),u=De("div"),d=Ce({},o.emoticons.dropdown,e?o.emoticons.more:{});return Fe(s,u),n=Math.sqrt(Object.keys(d).length),ze(s,"click","img",function(e){p.insert(l+Ae(this,"alt")+c,null,!1).closeDropDown(!0),e.preventDefault()}),Se(d,function(e,t){Fe(u,De("img",{src:r+(t.url||t),alt:e,title:t.tooltip||e})),u.children.length>=n&&(u=De("div"),Fe(s,u))}),!e&&o.emoticons.more&&(Fe(t=De("a",{className:"sceditor-more"}),document.createTextNode(p._("More"))),ze(t,"click",function(e){p.createDropDown(f,"more-emoticons",m(!0)),e.preventDefault()}),Fe(s,t)),s};p.createDropDown(f,"emoticons",m(!1))},txtExec:function(e){gt.emoticon.exec.call(this,e)},tooltip:"Insert an emoticon"},youtube:{_dropDown:function(i,e,a){var l=De("div");Fe(l,mt("youtubeMenu",{label:i._("Video URL:"),insert:i._("Insert")},!0)),ze(l,"click",".button",function(e){var t=He(l,"#link")[0].value,n=t.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/),o=t.match(/[&|?](?:star)?t=((\d+[hms]?){1,3})/),r=0;o&&Se(o[1].split(/[hms]/),function(e,t){""!==t&&(r=60*r+Number(t))}),n&&/^[a-zA-Z0-9_\-]{11}$/.test(n[1])&&a(n[1],r),i.closeDropDown(!0),e.preventDefault()}),i.createDropDown(e,"insertlink",l)},exec:function(e){var n=this;gt.youtube._dropDown(n,e,function(e,t){n.wysiwygEditorInsertHtml(mt("youtube",{id:e,time:t}))})},tooltip:"Insert a YouTube video"},date:{_date:function(e){var t=new Date,n=t.getYear(),o=t.getMonth()+1,r=t.getDate();return n<2e3&&(n=1900+n),o<10&&(o="0"+o),r<10&&(r="0"+r),e.opts.dateFormat.replace(/year/i,n).replace(/month/i,o).replace(/day/i,r)},exec:function(){this.insertText(gt.date._date(this))},txtExec:function(){this.insertText(gt.date._date(this))},tooltip:"Insert current date"},time:{_time:function(){var e=new Date,t=e.getHours(),n=e.getMinutes(),o=e.getSeconds();return t<10&&(t="0"+t),n<10&&(n="0"+n),o<10&&(o="0"+o),t+":"+n+":"+o},exec:function(){this.insertText(gt.time._time())},txtExec:function(){this.insertText(gt.time._time())},tooltip:"Insert current time"},ltr:{state:function(e,t){return t&&"ltr"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();(this.focus(),t&&!We(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!We(t,"body")))&&Pe(t,"direction","ltr"===Pe(t,"direction")?"":"ltr")},tooltip:"Left-to-Right"},rtl:{state:function(e,t){return t&&"rtl"===t.style.direction},exec:function(){var e=this.getRangeHelper(),t=e.getFirstBlockParent();(this.focus(),t&&!We(t,"body")||(this.execCommand("formatBlock","p"),(t=e.getFirstBlockParent())&&!We(t,"body")))&&Pe(t,"direction","rtl"===Pe(t,"direction")?"":"rtl")},tooltip:"Right-to-Left"},print:{exec:"print",tooltip:"Print"},maximize:{state:function(){return this.maximize()},exec:function(){this.maximize(!this.maximize())},txtExec:function(){this.maximize(!this.maximize())},tooltip:"Maximize",shortcut:"Ctrl+Shift+M"},source:{state:function(){return this.sourceMode()},exec:function(){this.toggleSourceMode()},txtExec:function(){this.toggleSourceMode()},tooltip:"View source",shortcut:"Ctrl+Shift+S"},ignore:{}},w={};function ht(i){var r=this,a=[],l=function(e){return"signal"+e.charAt(0).toUpperCase()+e.slice(1)},e=function(e,t){e=[].slice.call(e);var n,o,r=l(e.shift());for(n=0;nc.length&&s&&3===s.nodeType;)o=s.nodeValue,r=n-c.length,l&&(a=o.length,i=0),l=s,t?(u=i=Math.max(a-r,0),c=o.substr(i,a-i)+c,s=l.previousSibling):(u=i+(a=Math.min(r,o.length)),c+=o.substr(i,a),s=l.nextSibling);return{node:l||s,offset:u,text:c}};function vt(i,e){var a,l,c=e||i.contentDocument||i.document,s="sceditor-start-marker",u="sceditor-end-marker",y=this;y.insertHTML=function(e,t){var n,o;if(!y.selectedRange())return!1;for(t&&(e+=y.selectedHtml()+t),o=De("p",{},c),n=c.createDocumentFragment(),o.innerHTML=e;o.firstChild;)Fe(n,o.firstChild);y.insertNode(n)},l=function(e,t,n){var o,r=c.createDocumentFragment();if("string"==typeof e?(t&&(e+=y.selectedHtml()+t),r=et(e)):(Fe(r,e),t&&(Fe(r,y.selectedRange().extractContents()),Fe(r,t))),o=r.lastChild){for(;!rt(o.lastChild,!0);)o=o.lastChild;if(ot(o)?o.lastChild||Fe(o,document.createTextNode("​")):o=r,y.removeMarkers(),Fe(o,a(s)),Fe(o,a(u)),n){var i=De("div");return Fe(i,r),i.innerHTML}return r}},y.insertNode=function(e,t){var n=l(e,t),o=y.selectedRange(),r=o.commonAncestorContainer;if(!n)return!1;o.deleteContents(),r&&3!==r.nodeType&&!ot(r)?je(n,r):o.insertNode(n),y.restoreRange()},y.cloneSelected=function(){var e=y.selectedRange();if(e)return e.cloneRange()},y.selectedRange=function(){var e,t,n=i.getSelection();if(n){if(n.rangeCount<=0){for(t=c.body;t.firstChild;)t=t.firstChild;(e=c.createRange()).setStartBefore(t),n.addRange(e)}return 0'})},void n.readAsDataURL(t);a[l[s]]=i.getData(l[s])}a.text=a["text/plain"],a.html=a["text/html"],V(a)}else if(!T){var u=r.scrollTop;for(v.saveRange(),T=bt.createDocumentFragment();r.firstChild;)Fe(T,r.firstChild);setTimeout(function(){var e=r.innerHTML;r.innerHTML="",Fe(r,T),r.scrollTop=u,T=!1,v.restoreRange(),V({html:e})},0)}},V=function(e){var t=De("div",{},d);r.call("pasteRaw",e),Ge(w,"pasteraw",e),e.html?(t.innerHTML=e.html,it(t)):t.innerHTML=pt(e.text||"");var n={val:t.innerHTML};"fragmentToSource"in a&&(n.val=a.fragmentToSource(n.val,d,y)),r.call("paste",n),Ge(w,"paste",n),"fragmentToHtml"in a&&(n.val=a.fragmentToHtml(n.val,y)),r.call("pasteHtml",n),de.wysiwygEditorInsertHtml(n.val,null,!0)},de.closeDropDown=function(e){f&&(Re(f),f=null),!0===e&&de.focus()},de.wysiwygEditorInsertHtml=function(e,t,n){var o,r,i,a=Xe(c);de.focus(),!n&&Ne(b,"code")||(v.insertHTML(e,t),v.saveRange(),N(),Ie(o=He(m,"#sceditor-end-marker")[0]),r=m.scrollTop,i=lt(o).top+1.5*o.offsetHeight-a,Be(o),(r").replace(/&/g,"&")),de.wysiwygEditorInsertHtml(e),de},de.getWysiwygEditorValue=function(e){for(var t,n=De("div",{},d),o=m.childNodes,r=0;r"+(xt?"":"
    ")+"

    "),m.innerHTML=e,N(),Z(),ae(),ue()},de.setSourceEditorValue=function(e){s.value=e,ae()},de.updateOriginal=function(){l.value=de.val()},N=function(){var e,s,u,d,t,f,p;ye.emoticonsEnabled&&(e=m,s=ve,u=ye.emoticonsCompat,d=e.ownerDocument,t="(^|\\s| | | | |$)",f=[],p={},Me(e,"code")||(Se(s,function(e){p[e]=new RegExp(t+ft(e)+t),f.push(e)}),f.sort(function(e,t){return t.length-e.length}),function e(t){for(t=t.firstChild;t;){if(t.nodeType!==Ee||We(t,"code")||e(t),t.nodeType===Te)for(var n=0;n")}e.preventDefault()}}},Z=function(){Ze(m,function(e){if(e.nodeType===Ee&&!/inline/.test(Pe(e,"display"))&&!We(e,".sceditor-nlf")&&tt(e)){var t=De("p",{},d);return t.className="sceditor-nlf",t.innerHTML=wt?"":"
    ",Fe(m,t),!1}if(3===e.nodeType&&!/^\s*$/.test(e.nodeValue)||We(e,"br"))return!1})},U=function(){de.val(l.value)},$=function(){de.closeDropDown(),p=null},de._=function(){var n=arguments;return t&&t[n[0]]&&(n[0]=t[n[0]]),n[0].replace(/\{(\d+)\}/g,function(e,t){return void 0!==n[t-0+1]?n[t-0+1]:"{"+t+"}"})},K=function(t){r&&r.call(t.type+"Event",t,de);var e=(t.target===s?"scesrc":"scewys")+t.type;fe[e]&&fe[e].forEach(function(e){e.call(de,t)})},de.bind=function(e,t,n,o){for(var r=(e=e.split(" ")).length;r--;)if(we(t)){var i="scewys"+e[r],a="scesrc"+e[r];n||(fe[i]=fe[i]||[],fe[i].push(t)),o||(fe[a]=fe[a]||[],fe[a].push(t)),"valuechanged"===e[r]&&(ae.hasHandler=!0)}return de},de.unbind=function(e,t,n,o){for(var r=(e=e.split(" ")).length;r--;)we(t)&&(n||ke(fe["scewys"+e[r]]||[],t),o||ke(fe["scesrc"+e[r]]||[],t));return de},de.blur=function(e,t,n){return we(e)?de.bind("blur",e,t,n):de.sourceMode()?s.blur():m.blur(),de},de.focus=function(e,t,n){if(we(e))de.bind("focus",e,t,n);else if(de.inSourceMode())s.focus();else{if(He(d,":focus").length)return;var o,r=v.selectedRange();x||ne(),!wt&&r&&1===r.endOffset&&r.collapsed&&(o=r.endContainer)&&1===o.childNodes.length&&We(o.firstChild,"br")&&(r.setStartBefore(o.firstChild),r.collapse(!0),v.selectRange(r)),i.focus(),m.focus(),p&&(v.selectRange(p),p=null)}return J(),de},de.keyDown=function(e,t,n){return de.bind("keydown",e,t,n)},de.keyPress=function(e,t,n){return de.bind("keypress",e,t,n)},de.keyUp=function(e,t,n){return de.bind("keyup",e,t,n)},de.nodeChanged=function(e){return de.bind("nodechanged",e,!1,!0)},de.selectionChanged=function(e){return de.bind("selectionchanged",e,!1,!0)},de.valueChanged=function(e,t,n){return de.bind("valuechanged",e,t,n)},oe=function(e){var n=0,o=de.emoticonsCache,t=String.fromCharCode(e.which);Ne(b,"code")||(o||(o=[],Se(ve,function(e,t){o[n++]=[e,t]}),o.sort(function(e,t){return e[0].length-t[0].length}),de.emoticonsCache=o,de.longestEmoticonCode=o[o.length-1][0].length),v.replaceKeyword(de.emoticonsCache,!0,!0,de.longestEmoticonCode,ye.emoticonsCompat,t)&&(ye.emoticonsCompat&&/^\s$/.test(t)||e.preventDefault()))},re=function(){!function(e,t){var n=/[^\s\xA0\u2002\u2003\u2009\u00a0]+/,o=e&&He(e,"img[data-sceditor-emoticon]");if(e&&o.length)for(var r=0;r","/":"?","\\":"|","[":"{","]":"}"},o={109:"-",110:"del",111:"/",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9"},r=e.which,i={8:"backspace",9:"tab",13:"enter",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",91:"win",92:"win",93:"select",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scrolllock",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}[r]||String.fromCharCode(r).toLowerCase();(e.ctrlKey||e.metaKey)&&t.push("ctrl"),e.altKey&&t.push("alt"),e.shiftKey&&(t.push("shift"),o[r]?i=o[r]:n[i]&&(i=n[i])),i&&(r<16||18~jQ0e!@m| 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 R^t1o~002ovPDHLkV1hp-8@>Pl literal 0 HcmV?d00001 diff --git a/public/assets/minified/themes/modern.min.css b/public/assets/minified/themes/modern.min.css new file mode 100644 index 0000000..c870133 --- /dev/null +++ b/public/assets/minified/themes/modern.min.css @@ -0,0 +1 @@ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */.sceditor-button div,div.sceditor-grip{background-image:url(famfamfam.png);background-repeat:no-repeat;width:16px;height:16px}.sceditor-button-youtube div{background-position:0 0}.sceditor-button-link div{background-position:0 -16px}.sceditor-button-unlink div{background-position:0 -32px}.sceditor-button-underline div{background-position:0 -48px}.sceditor-button-time div{background-position:0 -64px}.sceditor-button-table div{background-position:0 -80px}.sceditor-button-superscript div{background-position:0 -96px}.sceditor-button-subscript div{background-position:0 -112px}.sceditor-button-strike div{background-position:0 -128px}.sceditor-button-source div{background-position:0 -144px}.sceditor-button-size div{background-position:0 -160px}.sceditor-button-rtl div{background-position:0 -176px}.sceditor-button-right div{background-position:0 -192px}.sceditor-button-removeformat div{background-position:0 -208px}.sceditor-button-quote div{background-position:0 -224px}.sceditor-button-print div{background-position:0 -240px}.sceditor-button-pastetext div{background-position:0 -256px}.sceditor-button-paste div{background-position:0 -272px}.sceditor-button-outdent div{background-position:0 -288px}.sceditor-button-orderedlist div{background-position:0 -304px}.sceditor-button-maximize div{background-position:0 -320px}.sceditor-button-ltr div{background-position:0 -336px}.sceditor-button-left div{background-position:0 -352px}.sceditor-button-justify div{background-position:0 -368px}.sceditor-button-italic div{background-position:0 -384px}.sceditor-button-indent div{background-position:0 -400px}.sceditor-button-image div{background-position:0 -416px}.sceditor-button-horizontalrule div{background-position:0 -432px}.sceditor-button-format div{background-position:0 -448px}.sceditor-button-font div{background-position:0 -464px}.sceditor-button-emoticon div{background-position:0 -480px}.sceditor-button-email div{background-position:0 -496px}.sceditor-button-date div{background-position:0 -512px}.sceditor-button-cut div{background-position:0 -528px}.sceditor-button-copy div{background-position:0 -544px}.sceditor-button-color div{background-position:0 -560px}.sceditor-button-code div{background-position:0 -576px}.sceditor-button-center div{background-position:0 -592px}.sceditor-button-bulletlist div{background-position:0 -608px}.sceditor-button-bold div{background-position:0 -624px}div.sceditor-grip{background-position:0 -640px}.rtl div.sceditor-grip{background-position:0 -650px;right:auto;left:0}.sceditor-container{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;position:relative;background:padding-box #fff;font-size:13px;font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;color:#333;line-height:1;font-weight:700;height:250px;border-radius:4px}.sceditor-container *,.sceditor-container :after,.sceditor-container :before{-webkit-box-sizing:content-box;box-sizing:content-box}.sceditor-container,.sceditor-container div,div.sceditor-dropdown,div.sceditor-dropdown div{padding:0;margin:0;z-index:3}.sceditor-container iframe,.sceditor-container textarea{display:block;-ms-flex:1 1 0%;-webkit-box-flex:1;flex:1 1 0%;line-height:1.25;border:0;outline:0;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;color:#111;padding:0;margin:5px;resize:none;background:#fff;height:auto!important;width:auto!important;width:calc(100% - 10px)!important;min-height:1px}div.sceditor-dnd-cover{position:absolute;top:0;left:0;bottom:0;right:0;background:rgba(255,255,255,.2);border:5px dashed #aaa;z-index:200;font-size:2em;text-align:center;color:#aaa}div.sceditor-dnd-cover p{position:relative;top:45%;pointer-events:none}div.sceditor-resize-cover{position:absolute;top:0;left:0;background:#000;width:100%;height:100%;z-index:10;opacity:.3}div.sceditor-grip{overflow:hidden;width:10px;height:10px;cursor:pointer;position:absolute;bottom:0;right:0;z-index:3;line-height:0}div.sceditor-grip.has-icon{background-image:none}.sceditor-maximize{position:fixed;top:0;left:0;height:100%!important;width:100%!important;border-radius:0;background-clip:padding-box;z-index:2000}body.sceditor-maximize,html.sceditor-maximize{height:100%;width:100%;padding:0;margin:0;overflow:hidden}.sceditor-maximize div.sceditor-grip{display:none}.sceditor-maximize div.sceditor-toolbar{border-radius:0;background-clip:padding-box}div.sceditor-dropdown{position:absolute;border:1px solid #ccc;background:padding-box #fff;z-index:4000;padding:10px;font-weight:400;font-size:15px;border-radius:2px;-webkit-box-shadow:1px 2px 4px rgba(0,0,0,.2);box-shadow:1px 2px 4px rgba(0,0,0,.2)}div.sceditor-dropdown *,div.sceditor-dropdown :after,div.sceditor-dropdown :before{-webkit-box-sizing:border-box;box-sizing:border-box}div.sceditor-dropdown a,div.sceditor-dropdown a:link{color:#333}div.sceditor-dropdown form{margin:0}div.sceditor-dropdown label{display:block;font-weight:700;color:#3c3c3c;padding:4px 0}div.sceditor-dropdown input,div.sceditor-dropdown textarea{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;outline:0;padding:4px;border:1px solid #ccc;border-top-color:#888;margin:0 0 .75em;border-radius:1px;background-clip:padding-box}div.sceditor-dropdown textarea{padding:6px}div.sceditor-dropdown input:focus,div.sceditor-dropdown textarea:focus{border-color:#666 #aaa #aaa;-webkit-box-shadow:inset 0 1px 5px rgba(0,0,0,.1);box-shadow:inset 0 1px 5px rgba(0,0,0,.1)}div.sceditor-dropdown .button{font-weight:700;color:#444;padding:6px 12px;background:padding-box #ececec;border:1px solid #ccc;border-radius:2px;cursor:pointer;margin:.3em 0 0}div.sceditor-dropdown .button:hover{background:#f3f3f3;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15);box-shadow:0 1px 1px rgba(0,0,0,.15)}div.sceditor-font-picker,div.sceditor-fontsize-picker,div.sceditor-format{padding:6px 0}div.sceditor-color-picker{padding:4px}div.sceditor-emoticons,div.sceditor-more-emoticons{padding:0}.sceditor-pastetext textarea{border:1px solid #bbb;width:20em}.sceditor-emoticons img,.sceditor-more-emoticons img{padding:0;cursor:pointer;margin:2px}.sceditor-more{border-top:1px solid #bbb;display:block;text-align:center;cursor:pointer;font-weight:700;padding:6px 0}.sceditor-dropdown a:hover{background:#eee}.sceditor-font-option,.sceditor-fontsize-option,.sceditor-format a{display:block;padding:7px 10px;cursor:pointer;text-decoration:none;color:#222}.sceditor-fontsize-option{padding:7px 13px}.sceditor-color-column{float:left}.sceditor-color-option{display:block;border:2px solid #fff;height:18px;width:18px;overflow:hidden}.sceditor-color-option:hover{border:1px solid #aaa}div.sceditor-toolbar{-ms-flex-negative:0;flex-shrink:0;overflow:hidden;padding:3px 5px 2px;border-bottom:1px solid silver;line-height:0;text-align:left;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:3px 3px 0 0}div.sceditor-group{border-bottom:1px solid #aaa;border-radius:3px}.sceditor-button.active,.sceditor-button:active,.sceditor-button:hover{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2)}.sceditor-button:active{background:#fff;-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3)}.sceditor-button.disabled:hover{background:inherit;cursor:default;-webkit-box-shadow:none;box-shadow:none}.sceditor-button,.sceditor-button div{display:block}.sceditor-button svg{display:inline-block;height:16px;width:16px;fill:#111;text-decoration:none;pointer-events:none;line-height:1}.sceditor-button.disabled svg{fill:#888}.sceditor-button div{display:inline-block;padding:0;overflow:hidden;line-height:0;font-size:0;color:transparent}.sceditor-button.has-icon div{display:none}.sceditor-button.disabled div{opacity:.3}.sceditor-button.text,.sceditor-button.text div,.sceditor-button.text-icon,.sceditor-button.text-icon div,.text .sceditor-button,.text .sceditor-button div,.text-icon .sceditor-button,.text-icon .sceditor-button div{display:inline-block;width:auto;line-height:16px;font-size:1em;color:inherit;text-indent:0}.sceditor-button.has-icon div,.sceditor-button.text div,.text .sceditor-button div,.text-icon .sceditor-button.has-icon div{padding:0 2px;background:0 0}.sceditor-button.text svg,.text .sceditor-button svg{display:none}.sceditor-button.text-icon div,.text-icon .sceditor-button div{padding:0 2px 0 20px}.rtl div.sceditor-toolbar{text-align:right}.rtl .sceditor-button{float:right}.sceditor-container{border:1px solid #999}.sceditor-container textarea{font-family:Consolas,"Bitstream Vera Sans Mono","Andale Mono",Monaco,"DejaVu Sans Mono","Lucida Console",monospace;background:#2e3436;color:#fff;margin:0;padding:5px}div.sceditor-toolbar{background:#ccc;background:-webkit-gradient(linear,left top,left bottom,from(#ccc),to(#b2b2b2));background:-webkit-linear-gradient(top,#ccc 0,#b2b2b2 100%);background:-o-linear-gradient(top,#ccc 0,#b2b2b2 100%);background:linear-gradient(to bottom,#ccc 0,#b2b2b2 100%)}div.sceditor-group{display:inline;background:0 0;margin:0;padding:0;border:0}.sceditor-button{float:left;cursor:pointer;width:16px;padding:4px;margin:2px 1px 2px 3px;height:16px;border-radius:12px;background-clip:padding-box}.sceditor-button.active,.sceditor-button.active:hover,.sceditor-button:hover{-webkit-box-shadow:none;box-shadow:none}.sceditor-button:hover{background:rgba(255,255,255,.75);margin:1px 0 1px 2px;border:1px solid #eee}.sceditor-button.disabled:hover{margin:2px 1px 2px 3px;border:0}.sceditor-button.active{background:rgba(0,0,0,.1);margin:1px 0 1px 2px;border:1px solid #999}.sceditor-button.active:hover{background:rgba(255,255,255,.25)}.sceditor-button.active:active,.sceditor-button:active{margin:1px 0 1px 2px;border:1px solid #999;-webkit-box-shadow:inset 0 0 4px rgba(0,0,0,.5);box-shadow:inset 0 0 4px rgba(0,0,0,.5)}.sceditor-button div,.sceditor-button svg{margin:0} \ No newline at end of file diff --git a/public/assets/minified/themes/office-toolbar.min.css b/public/assets/minified/themes/office-toolbar.min.css new file mode 100644 index 0000000..22d6606 --- /dev/null +++ b/public/assets/minified/themes/office-toolbar.min.css @@ -0,0 +1 @@ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */.sceditor-button div,div.sceditor-grip{background-image:url(famfamfam.png);background-repeat:no-repeat;width:16px;height:16px}.sceditor-button-youtube div{background-position:0 0}.sceditor-button-link div{background-position:0 -16px}.sceditor-button-unlink div{background-position:0 -32px}.sceditor-button-underline div{background-position:0 -48px}.sceditor-button-time div{background-position:0 -64px}.sceditor-button-table div{background-position:0 -80px}.sceditor-button-superscript div{background-position:0 -96px}.sceditor-button-subscript div{background-position:0 -112px}.sceditor-button-strike div{background-position:0 -128px}.sceditor-button-source div{background-position:0 -144px}.sceditor-button-size div{background-position:0 -160px}.sceditor-button-rtl div{background-position:0 -176px}.sceditor-button-right div{background-position:0 -192px}.sceditor-button-removeformat div{background-position:0 -208px}.sceditor-button-quote div{background-position:0 -224px}.sceditor-button-print div{background-position:0 -240px}.sceditor-button-pastetext div{background-position:0 -256px}.sceditor-button-paste div{background-position:0 -272px}.sceditor-button-outdent div{background-position:0 -288px}.sceditor-button-orderedlist div{background-position:0 -304px}.sceditor-button-maximize div{background-position:0 -320px}.sceditor-button-ltr div{background-position:0 -336px}.sceditor-button-left div{background-position:0 -352px}.sceditor-button-justify div{background-position:0 -368px}.sceditor-button-italic div{background-position:0 -384px}.sceditor-button-indent div{background-position:0 -400px}.sceditor-button-image div{background-position:0 -416px}.sceditor-button-horizontalrule div{background-position:0 -432px}.sceditor-button-format div{background-position:0 -448px}.sceditor-button-font div{background-position:0 -464px}.sceditor-button-emoticon div{background-position:0 -480px}.sceditor-button-email div{background-position:0 -496px}.sceditor-button-date div{background-position:0 -512px}.sceditor-button-cut div{background-position:0 -528px}.sceditor-button-copy div{background-position:0 -544px}.sceditor-button-color div{background-position:0 -560px}.sceditor-button-code div{background-position:0 -576px}.sceditor-button-center div{background-position:0 -592px}.sceditor-button-bulletlist div{background-position:0 -608px}.sceditor-button-bold div{background-position:0 -624px}div.sceditor-grip{background-position:0 -640px}.rtl div.sceditor-grip{background-position:0 -650px;right:auto;left:0}.sceditor-container{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;position:relative;background:padding-box #fff;font-size:13px;font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;color:#333;line-height:1;font-weight:700;height:250px;border-radius:4px}.sceditor-container *,.sceditor-container :after,.sceditor-container :before{-webkit-box-sizing:content-box;box-sizing:content-box}.sceditor-container,.sceditor-container div,div.sceditor-dropdown,div.sceditor-dropdown div{padding:0;margin:0;z-index:3}.sceditor-container iframe,.sceditor-container textarea{display:block;-ms-flex:1 1 0%;-webkit-box-flex:1;flex:1 1 0%;line-height:1.25;border:0;outline:0;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;color:#111;padding:0;margin:5px;resize:none;background:#fff;height:auto!important;width:auto!important;width:calc(100% - 10px)!important;min-height:1px}.sceditor-container textarea{margin:7px 5px;font-family:Consolas,"Bitstream Vera Sans Mono","Andale Mono",Monaco,"DejaVu Sans Mono","Lucida Console",monospace}div.sceditor-dnd-cover{position:absolute;top:0;left:0;bottom:0;right:0;background:rgba(255,255,255,.2);border:5px dashed #aaa;z-index:200;font-size:2em;text-align:center;color:#aaa}div.sceditor-dnd-cover p{position:relative;top:45%;pointer-events:none}div.sceditor-resize-cover{position:absolute;top:0;left:0;background:#000;width:100%;height:100%;z-index:10;opacity:.3}div.sceditor-grip{overflow:hidden;width:10px;height:10px;cursor:pointer;position:absolute;bottom:0;right:0;z-index:3;line-height:0}div.sceditor-grip.has-icon{background-image:none}.sceditor-maximize{position:fixed;top:0;left:0;height:100%!important;width:100%!important;border-radius:0;background-clip:padding-box;z-index:2000}body.sceditor-maximize,html.sceditor-maximize{height:100%;width:100%;padding:0;margin:0;overflow:hidden}.sceditor-maximize div.sceditor-grip{display:none}.sceditor-maximize div.sceditor-toolbar{border-radius:0;background-clip:padding-box}div.sceditor-dropdown{position:absolute;border:1px solid #ccc;background:padding-box #fff;z-index:4000;padding:10px;font-weight:400;font-size:15px;border-radius:2px;-webkit-box-shadow:1px 2px 4px rgba(0,0,0,.2);box-shadow:1px 2px 4px rgba(0,0,0,.2)}div.sceditor-dropdown *,div.sceditor-dropdown :after,div.sceditor-dropdown :before{-webkit-box-sizing:border-box;box-sizing:border-box}div.sceditor-dropdown a,div.sceditor-dropdown a:link{color:#333}div.sceditor-dropdown form{margin:0}div.sceditor-dropdown label{display:block;font-weight:700;color:#3c3c3c;padding:4px 0}div.sceditor-dropdown input,div.sceditor-dropdown textarea{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;outline:0;padding:4px;border:1px solid #ccc;border-top-color:#888;margin:0 0 .75em;border-radius:1px;background-clip:padding-box}div.sceditor-dropdown textarea{padding:6px}div.sceditor-dropdown input:focus,div.sceditor-dropdown textarea:focus{border-color:#666 #aaa #aaa;-webkit-box-shadow:inset 0 1px 5px rgba(0,0,0,.1);box-shadow:inset 0 1px 5px rgba(0,0,0,.1)}div.sceditor-dropdown .button{font-weight:700;color:#444;padding:6px 12px;background:padding-box #ececec;border:1px solid #ccc;border-radius:2px;cursor:pointer;margin:.3em 0 0}div.sceditor-dropdown .button:hover{background:#f3f3f3;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15);box-shadow:0 1px 1px rgba(0,0,0,.15)}div.sceditor-font-picker,div.sceditor-fontsize-picker,div.sceditor-format{padding:6px 0}div.sceditor-color-picker{padding:4px}div.sceditor-emoticons,div.sceditor-more-emoticons{padding:0}.sceditor-pastetext textarea{border:1px solid #bbb;width:20em}.sceditor-emoticons img,.sceditor-more-emoticons img{padding:0;cursor:pointer;margin:2px}.sceditor-more{border-top:1px solid #bbb;display:block;text-align:center;cursor:pointer;font-weight:700;padding:6px 0}.sceditor-dropdown a:hover{background:#eee}.sceditor-font-option,.sceditor-fontsize-option,.sceditor-format a{display:block;padding:7px 10px;cursor:pointer;text-decoration:none;color:#222}.sceditor-fontsize-option{padding:7px 13px}.sceditor-color-column{float:left}.sceditor-color-option{display:block;border:2px solid #fff;height:18px;width:18px;overflow:hidden}.sceditor-color-option:hover{border:1px solid #aaa}div.sceditor-toolbar{-ms-flex-negative:0;flex-shrink:0;overflow:hidden;padding:3px 5px 2px;line-height:0;text-align:left;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:3px 3px 0 0}div.sceditor-group{display:inline-block;margin:1px 5px 1px 0;border-bottom:1px solid #aaa;border-radius:3px}.sceditor-button.active,.sceditor-button:active,.sceditor-button:hover{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2)}.sceditor-button:active{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3)}.sceditor-button.disabled:hover{background:inherit;cursor:default;-webkit-box-shadow:none;box-shadow:none}.sceditor-button,.sceditor-button div{display:block}.sceditor-button svg{display:inline-block;height:16px;width:16px;fill:#111;text-decoration:none;pointer-events:none;line-height:1}.sceditor-button.disabled svg{fill:#888}.sceditor-button div{display:inline-block;padding:0;overflow:hidden;line-height:0;font-size:0;color:transparent}.sceditor-button.has-icon div{display:none}.sceditor-button.disabled div{opacity:.3}.sceditor-button.text,.sceditor-button.text div,.sceditor-button.text-icon,.sceditor-button.text-icon div,.text .sceditor-button,.text .sceditor-button div,.text-icon .sceditor-button,.text-icon .sceditor-button div{display:inline-block;width:auto;line-height:16px;font-size:1em;color:inherit;text-indent:0}.sceditor-button.has-icon div,.sceditor-button.text div,.text .sceditor-button div,.text-icon .sceditor-button.has-icon div{padding:0 2px;background:0 0}.sceditor-button.text svg,.text .sceditor-button svg{display:none}.sceditor-button.text-icon div,.text-icon .sceditor-button div{padding:0 2px 0 20px}.rtl div.sceditor-toolbar{text-align:right}.rtl .sceditor-button{float:right}.sceditor-container{border:1px solid #8db2e3}div.sceditor-toolbar{border-bottom:1px solid #95a9c3;background:#dee8f5;background:-webkit-gradient(linear,left top,left bottom,from(#dee8f5),color-stop(29%,#c7d8ed),color-stop(61%,#ccdcee),to(#c0d8ef));background:-webkit-linear-gradient(top,#dee8f5 0,#c7d8ed 29%,#ccdcee 61%,#c0d8ef 100%);background:-o-linear-gradient(top,#dee8f5 0,#c7d8ed 29%,#ccdcee 61%,#c0d8ef 100%);background:linear-gradient(to bottom,#dee8f5 0,#c7d8ed 29%,#ccdcee 61%,#c0d8ef 100%)}div.sceditor-group{border:1px solid #7596bf;background:#cadcf0;padding:0;background:-webkit-gradient(linear,left top,left bottom,color-stop(24%,#cadcf0),color-stop(38%,#bcd0e9),color-stop(99%,#d0e1f7));background:-webkit-linear-gradient(top,#cadcf0 24%,#bcd0e9 38%,#d0e1f7 99%);background:-o-linear-gradient(top,#cadcf0 24%,#bcd0e9 38%,#d0e1f7 99%);background:linear-gradient(to bottom,#cadcf0 24%,#bcd0e9 38%,#d0e1f7 99%)}.sceditor-button{float:left;cursor:pointer;width:16px;height:16px;padding:3px 4px;border-radius:0;background-clip:padding-box;-webkit-box-shadow:inset 0 1px #d5e3f1,inset 0 -1px #e3edfb,inset 1px 0 #cddcef,inset -1px 0 #b8ceea;box-shadow:inset 0 1px #d5e3f1,inset 0 -1px #e3edfb,inset 1px 0 #cddcef,inset -1px 0 #b8ceea}.sceditor-button:first-child{border-radius:4px 0 0 4px;background-clip:padding-box}.sceditor-button:last-child{border-radius:0 4px 4px 0;background-clip:padding-box}.sceditor-button div,.sceditor-button svg{margin:0}.sceditor-button.active{background:#fbdbb5;background:-webkit-gradient(linear,left top,left bottom,color-stop(11%,#fbdbb5),color-stop(29%,#feb456),color-stop(99%,#fdeb9f));background:-webkit-linear-gradient(top,#fbdbb5 11%,#feb456 29%,#fdeb9f 99%);background:-o-linear-gradient(top,#fbdbb5 11%,#feb456 29%,#fdeb9f 99%);background:linear-gradient(to bottom,#fbdbb5 11%,#feb456 29%,#fdeb9f 99%);-webkit-box-shadow:inset 0 1px #ebd1b4,inset 0 -1px #ffe47f,inset -1px 0 #b8ceea;box-shadow:inset 0 1px #ebd1b4,inset 0 -1px #ffe47f,inset -1px 0 #b8ceea}.sceditor-button:hover{background:#fef7d5;background:-webkit-gradient(linear,left top,left bottom,from(#fef7d5),color-stop(42%,#fae5a9),color-stop(42%,#ffd048),to(#ffe59f));background:-webkit-linear-gradient(top,#fef7d5 0,#fae5a9 42%,#ffd048 42%,#ffe59f 100%);background:-o-linear-gradient(top,#fef7d5 0,#fae5a9 42%,#ffd048 42%,#ffe59f 100%);background:linear-gradient(to bottom,#fef7d5 0,#fae5a9 42%,#ffd048 42%,#ffe59f 100%);-webkit-box-shadow:inset 0 1px #fffbe8,inset -1px 0 #ffefc4,inset 0 -1px #fff9cc;box-shadow:inset 0 1px #fffbe8,inset -1px 0 #ffefc4,inset 0 -1px #fff9cc}.sceditor-button:active{background:#e7a66d;background:-webkit-gradient(linear,left top,left bottom,from(#e7a66d),color-stop(1%,#fcb16d),color-stop(42%,#ff8d05),to(#ffc450));background:-webkit-linear-gradient(top,#e7a66d 0,#fcb16d 1%,#ff8d05 42%,#ffc450 100%);background:-o-linear-gradient(top,#e7a66d 0,#fcb16d 1%,#ff8d05 42%,#ffc450 100%);background:linear-gradient(to bottom,#e7a66d 0,#fcb16d 1%,#ff8d05 42%,#ffc450 100%);-webkit-box-shadow:inset 0 1px 1px #7b6645,inset 0 -1px #d19c33;box-shadow:inset 0 1px 1px #7b6645,inset 0 -1px #d19c33}.sceditor-button.active:hover{background:#dba368;background:-webkit-gradient(linear,left top,left bottom,from(#dba368),color-stop(4%,#ffbd79),color-stop(34%,#fea335),color-stop(66%,#ffc64c),to(#fee069));background:-webkit-linear-gradient(top,#dba368 0,#ffbd79 4%,#fea335 34%,#ffc64c 66%,#fee069 100%);background:-o-linear-gradient(top,#dba368 0,#ffbd79 4%,#fea335 34%,#ffc64c 66%,#fee069 100%);background:linear-gradient(to bottom,#dba368 0,#ffbd79 4%,#fea335 34%,#ffc64c 66%,#fee069 100%);-webkit-box-shadow:inset 0 1px 1px #9e8255,inset 0 -1px #fcce6b;box-shadow:inset 0 1px 1px #9e8255,inset 0 -1px #fcce6b} \ No newline at end of file diff --git a/public/assets/minified/themes/office.min.css b/public/assets/minified/themes/office.min.css new file mode 100644 index 0000000..71f7dea --- /dev/null +++ b/public/assets/minified/themes/office.min.css @@ -0,0 +1 @@ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */.sceditor-button div,div.sceditor-grip{background-image:url(famfamfam.png);background-repeat:no-repeat;width:16px;height:16px}.sceditor-button-youtube div{background-position:0 0}.sceditor-button-link div{background-position:0 -16px}.sceditor-button-unlink div{background-position:0 -32px}.sceditor-button-underline div{background-position:0 -48px}.sceditor-button-time div{background-position:0 -64px}.sceditor-button-table div{background-position:0 -80px}.sceditor-button-superscript div{background-position:0 -96px}.sceditor-button-subscript div{background-position:0 -112px}.sceditor-button-strike div{background-position:0 -128px}.sceditor-button-source div{background-position:0 -144px}.sceditor-button-size div{background-position:0 -160px}.sceditor-button-rtl div{background-position:0 -176px}.sceditor-button-right div{background-position:0 -192px}.sceditor-button-removeformat div{background-position:0 -208px}.sceditor-button-quote div{background-position:0 -224px}.sceditor-button-print div{background-position:0 -240px}.sceditor-button-pastetext div{background-position:0 -256px}.sceditor-button-paste div{background-position:0 -272px}.sceditor-button-outdent div{background-position:0 -288px}.sceditor-button-orderedlist div{background-position:0 -304px}.sceditor-button-maximize div{background-position:0 -320px}.sceditor-button-ltr div{background-position:0 -336px}.sceditor-button-left div{background-position:0 -352px}.sceditor-button-justify div{background-position:0 -368px}.sceditor-button-italic div{background-position:0 -384px}.sceditor-button-indent div{background-position:0 -400px}.sceditor-button-image div{background-position:0 -416px}.sceditor-button-horizontalrule div{background-position:0 -432px}.sceditor-button-format div{background-position:0 -448px}.sceditor-button-font div{background-position:0 -464px}.sceditor-button-emoticon div{background-position:0 -480px}.sceditor-button-email div{background-position:0 -496px}.sceditor-button-date div{background-position:0 -512px}.sceditor-button-cut div{background-position:0 -528px}.sceditor-button-copy div{background-position:0 -544px}.sceditor-button-color div{background-position:0 -560px}.sceditor-button-code div{background-position:0 -576px}.sceditor-button-center div{background-position:0 -592px}.sceditor-button-bulletlist div{background-position:0 -608px}.sceditor-button-bold div{background-position:0 -624px}div.sceditor-grip{background-position:0 -640px}.rtl div.sceditor-grip{background-position:0 -650px;right:auto;left:0}.sceditor-container{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;position:relative;font-size:13px;font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;color:#333;line-height:1;font-weight:700;height:250px;border-radius:4px}.sceditor-container *,.sceditor-container :after,.sceditor-container :before{-webkit-box-sizing:content-box;box-sizing:content-box}.sceditor-container,.sceditor-container div,div.sceditor-dropdown,div.sceditor-dropdown div{padding:0;margin:0;z-index:3}.sceditor-container iframe,.sceditor-container textarea{display:block;-ms-flex:1 1 0%;-webkit-box-flex:1;flex:1 1 0%;line-height:1.25;outline:0;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;color:#111;resize:none;height:auto!important;width:auto!important;min-height:1px}div.sceditor-dnd-cover{position:absolute;top:0;left:0;bottom:0;right:0;background:rgba(255,255,255,.2);border:5px dashed #aaa;z-index:200;font-size:2em;text-align:center;color:#aaa}div.sceditor-dnd-cover p{position:relative;top:45%;pointer-events:none}div.sceditor-resize-cover{position:absolute;top:0;left:0;background:#000;width:100%;height:100%;z-index:10;opacity:.3}div.sceditor-grip{overflow:hidden;width:10px;height:10px;cursor:pointer;position:absolute;bottom:0;right:0;z-index:3;line-height:0}div.sceditor-grip.has-icon{background-image:none}.sceditor-maximize{position:fixed;top:0;left:0;height:100%!important;width:100%!important;border-radius:0;background-clip:padding-box;z-index:2000}body.sceditor-maximize,html.sceditor-maximize{height:100%;width:100%;padding:0;margin:0;overflow:hidden}.sceditor-maximize div.sceditor-grip{display:none}.sceditor-maximize div.sceditor-toolbar{border-radius:0;background-clip:padding-box}div.sceditor-dropdown{position:absolute;border:1px solid #ccc;background:padding-box #fff;z-index:4000;padding:10px;font-weight:400;font-size:15px;border-radius:2px;-webkit-box-shadow:1px 2px 4px rgba(0,0,0,.2);box-shadow:1px 2px 4px rgba(0,0,0,.2)}div.sceditor-dropdown *,div.sceditor-dropdown :after,div.sceditor-dropdown :before{-webkit-box-sizing:border-box;box-sizing:border-box}div.sceditor-dropdown a,div.sceditor-dropdown a:link{color:#333}div.sceditor-dropdown form{margin:0}div.sceditor-dropdown label{display:block;font-weight:700;color:#3c3c3c;padding:4px 0}div.sceditor-dropdown input,div.sceditor-dropdown textarea{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;outline:0;padding:4px;border:1px solid #ccc;border-top-color:#888;margin:0 0 .75em;border-radius:1px;background-clip:padding-box}div.sceditor-dropdown textarea{padding:6px}div.sceditor-dropdown input:focus,div.sceditor-dropdown textarea:focus{border-color:#666 #aaa #aaa;-webkit-box-shadow:inset 0 1px 5px rgba(0,0,0,.1);box-shadow:inset 0 1px 5px rgba(0,0,0,.1)}div.sceditor-dropdown .button{font-weight:700;color:#444;padding:6px 12px;background:padding-box #ececec;border:1px solid #ccc;border-radius:2px;cursor:pointer;margin:.3em 0 0}div.sceditor-dropdown .button:hover{background:#f3f3f3;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15);box-shadow:0 1px 1px rgba(0,0,0,.15)}div.sceditor-font-picker,div.sceditor-fontsize-picker,div.sceditor-format{padding:6px 0}div.sceditor-color-picker{padding:4px}div.sceditor-emoticons,div.sceditor-more-emoticons{padding:0}.sceditor-pastetext textarea{border:1px solid #bbb;width:20em}.sceditor-emoticons img,.sceditor-more-emoticons img{padding:0;cursor:pointer;margin:2px}.sceditor-more{border-top:1px solid #bbb;display:block;text-align:center;cursor:pointer;font-weight:700;padding:6px 0}.sceditor-dropdown a:hover{background:#eee}.sceditor-font-option,.sceditor-fontsize-option,.sceditor-format a{display:block;padding:7px 10px;cursor:pointer;text-decoration:none;color:#222}.sceditor-fontsize-option{padding:7px 13px}.sceditor-color-column{float:left}.sceditor-color-option{display:block;border:2px solid #fff;height:18px;width:18px;overflow:hidden}.sceditor-color-option:hover{border:1px solid #aaa}div.sceditor-toolbar{-ms-flex-negative:0;flex-shrink:0;overflow:hidden;padding:3px 5px 2px;line-height:0;text-align:left;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:3px 3px 0 0}div.sceditor-group{display:inline-block;margin:1px 5px 1px 0;border-bottom:1px solid #aaa;border-radius:3px}.sceditor-button.active,.sceditor-button:active,.sceditor-button:hover{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2)}.sceditor-button:active{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3)}.sceditor-button.disabled:hover{background:inherit;cursor:default;-webkit-box-shadow:none;box-shadow:none}.sceditor-button,.sceditor-button div{display:block}.sceditor-button svg{display:inline-block;height:16px;width:16px;fill:#111;text-decoration:none;pointer-events:none;line-height:1}.sceditor-button.disabled svg{fill:#888}.sceditor-button div{display:inline-block;padding:0;overflow:hidden;line-height:0;font-size:0;color:transparent}.sceditor-button.has-icon div{display:none}.sceditor-button.disabled div{opacity:.3}.sceditor-button.text,.sceditor-button.text div,.sceditor-button.text-icon,.sceditor-button.text-icon div,.text .sceditor-button,.text .sceditor-button div,.text-icon .sceditor-button,.text-icon .sceditor-button div{display:inline-block;width:auto;line-height:16px;font-size:1em;color:inherit;text-indent:0}.sceditor-button.has-icon div,.sceditor-button.text div,.text .sceditor-button div,.text-icon .sceditor-button.has-icon div{padding:0 2px;background:0 0}.sceditor-button.text svg,.text .sceditor-button svg{display:none}.sceditor-button.text-icon div,.text-icon .sceditor-button div{padding:0 2px 0 20px}.rtl div.sceditor-toolbar{text-align:right}.rtl .sceditor-button{float:right}.sceditor-container{border:1px solid #8db2e3}.sceditor-container textarea{font-family:Consolas,"Bitstream Vera Sans Mono","Andale Mono",Monaco,"DejaVu Sans Mono","Lucida Console",monospace}div.sceditor-toolbar{border-bottom:1px solid #95a9c3;background:#dee8f5;background:-webkit-gradient(linear,left top,left bottom,from(#dee8f5),color-stop(29%,#c7d8ed),color-stop(61%,#ccdcee),to(#c0d8ef));background:-webkit-linear-gradient(top,#dee8f5 0,#c7d8ed 29%,#ccdcee 61%,#c0d8ef 100%);background:-o-linear-gradient(top,#dee8f5 0,#c7d8ed 29%,#ccdcee 61%,#c0d8ef 100%);background:linear-gradient(to bottom,#dee8f5 0,#c7d8ed 29%,#ccdcee 61%,#c0d8ef 100%)}div.sceditor-group{border:1px solid #7596bf;background:#cadcf0;padding:0;background:-webkit-gradient(linear,left top,left bottom,color-stop(24%,#cadcf0),color-stop(38%,#bcd0e9),color-stop(99%,#d0e1f7));background:-webkit-linear-gradient(top,#cadcf0 24%,#bcd0e9 38%,#d0e1f7 99%);background:-o-linear-gradient(top,#cadcf0 24%,#bcd0e9 38%,#d0e1f7 99%);background:linear-gradient(to bottom,#cadcf0 24%,#bcd0e9 38%,#d0e1f7 99%)}.sceditor-button{float:left;cursor:pointer;width:16px;height:16px;padding:3px 4px;border-radius:0;background-clip:padding-box;-webkit-box-shadow:inset 0 1px #d5e3f1,inset 0 -1px #e3edfb,inset 1px 0 #cddcef,inset -1px 0 #b8ceea;box-shadow:inset 0 1px #d5e3f1,inset 0 -1px #e3edfb,inset 1px 0 #cddcef,inset -1px 0 #b8ceea}.sceditor-button:first-child{border-radius:4px 0 0 4px;background-clip:padding-box}.sceditor-button:last-child{border-radius:0 4px 4px 0;background-clip:padding-box}.sceditor-button div,.sceditor-button svg{margin:0}.sceditor-button.active{background:#fbdbb5;background:-webkit-gradient(linear,left top,left bottom,color-stop(11%,#fbdbb5),color-stop(29%,#feb456),color-stop(99%,#fdeb9f));background:-webkit-linear-gradient(top,#fbdbb5 11%,#feb456 29%,#fdeb9f 99%);background:-o-linear-gradient(top,#fbdbb5 11%,#feb456 29%,#fdeb9f 99%);background:linear-gradient(to bottom,#fbdbb5 11%,#feb456 29%,#fdeb9f 99%);-webkit-box-shadow:inset 0 1px #ebd1b4,inset 0 -1px #ffe47f,inset -1px 0 #b8ceea;box-shadow:inset 0 1px #ebd1b4,inset 0 -1px #ffe47f,inset -1px 0 #b8ceea}.sceditor-button:hover{background:#fef7d5;background:-webkit-gradient(linear,left top,left bottom,from(#fef7d5),color-stop(42%,#fae5a9),color-stop(42%,#ffd048),to(#ffe59f));background:-webkit-linear-gradient(top,#fef7d5 0,#fae5a9 42%,#ffd048 42%,#ffe59f 100%);background:-o-linear-gradient(top,#fef7d5 0,#fae5a9 42%,#ffd048 42%,#ffe59f 100%);background:linear-gradient(to bottom,#fef7d5 0,#fae5a9 42%,#ffd048 42%,#ffe59f 100%);-webkit-box-shadow:inset 0 1px #fffbe8,inset -1px 0 #ffefc4,inset 0 -1px #fff9cc;box-shadow:inset 0 1px #fffbe8,inset -1px 0 #ffefc4,inset 0 -1px #fff9cc}.sceditor-button:active{background:#e7a66d;background:-webkit-gradient(linear,left top,left bottom,from(#e7a66d),color-stop(1%,#fcb16d),color-stop(42%,#ff8d05),to(#ffc450));background:-webkit-linear-gradient(top,#e7a66d 0,#fcb16d 1%,#ff8d05 42%,#ffc450 100%);background:-o-linear-gradient(top,#e7a66d 0,#fcb16d 1%,#ff8d05 42%,#ffc450 100%);background:linear-gradient(to bottom,#e7a66d 0,#fcb16d 1%,#ff8d05 42%,#ffc450 100%);-webkit-box-shadow:inset 0 1px 1px #7b6645,inset 0 -1px #d19c33;box-shadow:inset 0 1px 1px #7b6645,inset 0 -1px #d19c33}.sceditor-button.active:hover{background:#dba368;background:-webkit-gradient(linear,left top,left bottom,from(#dba368),color-stop(4%,#ffbd79),color-stop(34%,#fea335),color-stop(66%,#ffc64c),to(#fee069));background:-webkit-linear-gradient(top,#dba368 0,#ffbd79 4%,#fea335 34%,#ffc64c 66%,#fee069 100%);background:-o-linear-gradient(top,#dba368 0,#ffbd79 4%,#fea335 34%,#ffc64c 66%,#fee069 100%);background:linear-gradient(to bottom,#dba368 0,#ffbd79 4%,#fea335 34%,#ffc64c 66%,#fee069 100%);-webkit-box-shadow:inset 0 1px 1px #9e8255,inset 0 -1px #fcce6b;box-shadow:inset 0 1px 1px #9e8255,inset 0 -1px #fcce6b}.sceditor-container{background:#a3c2ea;background:-webkit-gradient(linear,left top,left bottom,from(#a3c2ea),color-stop(39%,#6d92c1),color-stop(64%,#577fb3),to(#6591cc));background:-webkit-linear-gradient(top,#a3c2ea 0,#6d92c1 39%,#577fb3 64%,#6591cc 100%);background:-o-linear-gradient(top,#a3c2ea 0,#6d92c1 39%,#577fb3 64%,#6591cc 100%);background:linear-gradient(to bottom,#a3c2ea 0,#6d92c1 39%,#577fb3 64%,#6591cc 100%)}.sceditor-container iframe,.sceditor-container textarea{border:1px solid #646464;background:#fff;margin:7px 40px;padding:20px;width:calc(100% - 120px)!important;-webkit-box-shadow:1px 1px 5px #293a52;box-shadow:1px 1px 5px #293a52} \ No newline at end of file diff --git a/public/assets/minified/themes/square.min.css b/public/assets/minified/themes/square.min.css new file mode 100644 index 0000000..0b67f7d --- /dev/null +++ b/public/assets/minified/themes/square.min.css @@ -0,0 +1 @@ +/*! SCEditor | (C) 2011-2016, Sam Clarke | sceditor.com/license */.sceditor-button div,div.sceditor-grip{background-image:url(famfamfam.png);background-repeat:no-repeat;width:16px;height:16px}.sceditor-button-youtube div{background-position:0 0}.sceditor-button-link div{background-position:0 -16px}.sceditor-button-unlink div{background-position:0 -32px}.sceditor-button-underline div{background-position:0 -48px}.sceditor-button-time div{background-position:0 -64px}.sceditor-button-table div{background-position:0 -80px}.sceditor-button-superscript div{background-position:0 -96px}.sceditor-button-subscript div{background-position:0 -112px}.sceditor-button-strike div{background-position:0 -128px}.sceditor-button-source div{background-position:0 -144px}.sceditor-button-size div{background-position:0 -160px}.sceditor-button-rtl div{background-position:0 -176px}.sceditor-button-right div{background-position:0 -192px}.sceditor-button-removeformat div{background-position:0 -208px}.sceditor-button-quote div{background-position:0 -224px}.sceditor-button-print div{background-position:0 -240px}.sceditor-button-pastetext div{background-position:0 -256px}.sceditor-button-paste div{background-position:0 -272px}.sceditor-button-outdent div{background-position:0 -288px}.sceditor-button-orderedlist div{background-position:0 -304px}.sceditor-button-maximize div{background-position:0 -320px}.sceditor-button-ltr div{background-position:0 -336px}.sceditor-button-left div{background-position:0 -352px}.sceditor-button-justify div{background-position:0 -368px}.sceditor-button-italic div{background-position:0 -384px}.sceditor-button-indent div{background-position:0 -400px}.sceditor-button-image div{background-position:0 -416px}.sceditor-button-horizontalrule div{background-position:0 -432px}.sceditor-button-format div{background-position:0 -448px}.sceditor-button-font div{background-position:0 -464px}.sceditor-button-emoticon div{background-position:0 -480px}.sceditor-button-email div{background-position:0 -496px}.sceditor-button-date div{background-position:0 -512px}.sceditor-button-cut div{background-position:0 -528px}.sceditor-button-copy div{background-position:0 -544px}.sceditor-button-color div{background-position:0 -560px}.sceditor-button-code div{background-position:0 -576px}.sceditor-button-center div{background-position:0 -592px}.sceditor-button-bulletlist div{background-position:0 -608px}.sceditor-button-bold div{background-position:0 -624px}div.sceditor-grip{background-position:0 -640px}.rtl div.sceditor-grip{background-position:0 -650px;right:auto;left:0}.sceditor-container{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-direction:column;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;position:relative;background:padding-box #fff;font-size:13px;font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;color:#333;line-height:1;font-weight:700;height:250px}.sceditor-container *,.sceditor-container :after,.sceditor-container :before{-webkit-box-sizing:content-box;box-sizing:content-box}.sceditor-container,.sceditor-container div,div.sceditor-dropdown,div.sceditor-dropdown div{padding:0;margin:0;z-index:3}.sceditor-container iframe,.sceditor-container textarea{display:block;-ms-flex:1 1 0%;-webkit-box-flex:1;flex:1 1 0%;line-height:1.25;border:0;outline:0;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;color:#111;padding:0;margin:5px;resize:none;background:#fff;height:auto!important;width:auto!important;width:calc(100% - 10px)!important;min-height:1px}div.sceditor-dnd-cover{position:absolute;top:0;left:0;bottom:0;right:0;background:rgba(255,255,255,.2);border:5px dashed #aaa;z-index:200;font-size:2em;text-align:center;color:#aaa}div.sceditor-dnd-cover p{position:relative;top:45%;pointer-events:none}div.sceditor-resize-cover{position:absolute;top:0;left:0;background:#000;width:100%;height:100%;z-index:10;opacity:.3}div.sceditor-grip{overflow:hidden;width:10px;height:10px;cursor:pointer;position:absolute;bottom:0;right:0;z-index:3;line-height:0}div.sceditor-grip.has-icon{background-image:none}.sceditor-maximize{position:fixed;top:0;left:0;height:100%!important;width:100%!important;border-radius:0;background-clip:padding-box;z-index:2000}body.sceditor-maximize,html.sceditor-maximize{height:100%;width:100%;padding:0;margin:0;overflow:hidden}.sceditor-maximize div.sceditor-grip{display:none}.sceditor-maximize div.sceditor-toolbar{border-radius:0;background-clip:padding-box}div.sceditor-dropdown{position:absolute;border:1px solid #ccc;background:padding-box #fff;z-index:4000;padding:10px;font-weight:400;font-size:15px;border-radius:2px;-webkit-box-shadow:1px 2px 4px rgba(0,0,0,.2);box-shadow:1px 2px 4px rgba(0,0,0,.2)}div.sceditor-dropdown *,div.sceditor-dropdown :after,div.sceditor-dropdown :before{-webkit-box-sizing:border-box;box-sizing:border-box}div.sceditor-dropdown a,div.sceditor-dropdown a:link{color:#333}div.sceditor-dropdown form{margin:0}div.sceditor-dropdown label{display:block;font-weight:700;color:#3c3c3c;padding:4px 0}div.sceditor-dropdown input,div.sceditor-dropdown textarea{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;outline:0;padding:4px;border:1px solid #ccc;border-top-color:#888;margin:0 0 .75em;border-radius:1px;background-clip:padding-box}div.sceditor-dropdown textarea{padding:6px}div.sceditor-dropdown input:focus,div.sceditor-dropdown textarea:focus{border-color:#666 #aaa #aaa;-webkit-box-shadow:inset 0 1px 5px rgba(0,0,0,.1);box-shadow:inset 0 1px 5px rgba(0,0,0,.1)}div.sceditor-dropdown .button{font-weight:700;color:#444;padding:6px 12px;background:padding-box #ececec;border:1px solid #ccc;border-radius:2px;cursor:pointer;margin:.3em 0 0}div.sceditor-dropdown .button:hover{background:#f3f3f3;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.15);box-shadow:0 1px 1px rgba(0,0,0,.15)}div.sceditor-font-picker,div.sceditor-fontsize-picker,div.sceditor-format{padding:6px 0}div.sceditor-color-picker{padding:4px}div.sceditor-emoticons,div.sceditor-more-emoticons{padding:0}.sceditor-pastetext textarea{border:1px solid #bbb;width:20em}.sceditor-emoticons img,.sceditor-more-emoticons img{padding:0;cursor:pointer;margin:2px}.sceditor-more{border-top:1px solid #bbb;display:block;text-align:center;cursor:pointer;font-weight:700;padding:6px 0}.sceditor-dropdown a:hover{background:#eee}.sceditor-font-option,.sceditor-fontsize-option,.sceditor-format a{display:block;padding:7px 10px;cursor:pointer;text-decoration:none;color:#222}.sceditor-fontsize-option{padding:7px 13px}.sceditor-color-column{float:left}.sceditor-color-option{display:block;border:2px solid #fff;height:18px;width:18px;overflow:hidden}.sceditor-color-option:hover{border:1px solid #aaa}div.sceditor-toolbar{-ms-flex-negative:0;flex-shrink:0;overflow:hidden;line-height:0;text-align:left;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-radius:3px 3px 0 0}div.sceditor-group{display:inline-block;border-bottom:1px solid #aaa}.sceditor-button.active,.sceditor-button:active,.sceditor-button:hover{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2)}.sceditor-button:active{-webkit-box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3);box-shadow:inset 1px 1px 0 rgba(0,0,0,.3),inset -1px 0 rgba(0,0,0,.3),inset 0 -1px 0 rgba(0,0,0,.2),inset 0 0 8px rgba(0,0,0,.3)}.sceditor-button.disabled:hover{background:inherit;cursor:default;-webkit-box-shadow:none;box-shadow:none}.sceditor-button,.sceditor-button div{display:block}.sceditor-button svg{display:inline-block;height:16px;width:16px;fill:#111;text-decoration:none;pointer-events:none;line-height:1}.sceditor-button.disabled svg{fill:#888}.sceditor-button div{display:inline-block;padding:0;overflow:hidden;line-height:0;font-size:0;color:transparent}.sceditor-button.has-icon div{display:none}.sceditor-button.disabled div{opacity:.3}.sceditor-button.text,.sceditor-button.text div,.sceditor-button.text-icon,.sceditor-button.text-icon div,.text .sceditor-button,.text .sceditor-button div,.text-icon .sceditor-button,.text-icon .sceditor-button div{display:inline-block;width:auto;line-height:16px;font-size:1em;color:inherit;text-indent:0}.sceditor-button.has-icon div,.sceditor-button.text div,.text .sceditor-button div,.text-icon .sceditor-button.has-icon div{padding:0 2px;background:0 0}.sceditor-button.text svg,.text .sceditor-button svg{display:none}.sceditor-button.text-icon div,.text-icon .sceditor-button div{padding:0 2px 0 20px}.rtl div.sceditor-toolbar{text-align:right}.rtl .sceditor-button{float:right}.sceditor-container{border:1px solid #d6d6d6;border-radius:0;background-clip:padding-box}.sceditor-container textarea{font-family:Consolas,"Bitstream Vera Sans Mono","Andale Mono",Monaco,"DejaVu Sans Mono","Lucida Console",monospace;background:#2e3436;color:#fff;margin:0;padding:5px}div.sceditor-group,div.sceditor-toolbar{background:#f2f2f2;background:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),color-stop(89%,#ddd));background:-webkit-linear-gradient(top,#f2f2f2 0,#ddd 89%);background:-o-linear-gradient(top,#f2f2f2 0,#ddd 89%);background:linear-gradient(to bottom,#f2f2f2 0,#ddd 89%)}div.sceditor-toolbar{padding:0;border-bottom:1px solid #bbb;background-size:100% 32px}div.sceditor-group{margin:0;padding:2px 4px;border:0;border-right:1px solid #ccc;border-left:1px solid #eaeaea;border-radius:0;background-clip:padding-box}div.sceditor-group:last-child{border-right:0}div.sceditor-group:first-child{border-left:0}.sceditor-button{float:left;cursor:pointer;width:16px;height:16px;padding:5px;margin:1px;border-radius:0;background-clip:padding-box}.sceditor-button div,.sceditor-button svg{margin:0}.sceditor-button.active,.sceditor-button.active:hover,.sceditor-button:active,.sceditor-button:hover{margin:0;-webkit-box-shadow:none;box-shadow:none}.sceditor-button.active{background:#f4f4f4;border:1px solid #ccc}.sceditor-button:hover{background:#fefefe;border:1px solid #ddd}.sceditor-button.disabled:hover{margin:1px;border:0}.sceditor-button:active{background:#eee;border:1px solid #ccc}.sceditor-button.active:hover{background:#f8f8f8;border:1px solid #ddd} \ No newline at end of file diff --git a/resources/views.zip b/resources/views.zip new file mode 100644 index 0000000000000000000000000000000000000000..9122c117bfb9bb191aac82c776e83b4c437d55e9 GIT binary patch literal 35185 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 f=TV+xs@-<|;(!A-)
    -
    + @csrf

    ThemePark

    diff --git a/resources/views/control.blade.php b/resources/views/control.blade.php new file mode 100644 index 0000000..e82884b --- /dev/null +++ b/resources/views/control.blade.php @@ -0,0 +1,55 @@ +@extends('layouts.main') + +@section('content') +
    +
    +
    +
    +
    +

    Information

    +
    +
    +
    + +
    +

    Username: {{ Auth::user()->username() }} +
    Attraction: {{ $attraction_name }}

    +

    Control: Disconnected

    +

    Status: Unknown

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +@endsection + +@section('javascript') + + +@endsection diff --git a/resources/views/emails/change.blade.php b/resources/views/emails/change.blade.php new file mode 100644 index 0000000..d8e150d --- /dev/null +++ b/resources/views/emails/change.blade.php @@ -0,0 +1,13 @@ +@component('mail::message') +Dear {{ $user->firstname }} + +Press the button bellow if you wish to change your current email-address +@component('mail::button', ['url' => route('verify_email', ['id' => $user->id, 'token' => $token, 'email' => $email])]) +Change Email +@endcomponent + +Is this email not directed to you or do you not wish to change your email? Than you may ignore this. + +Thanks,
    +{{ config('app.name') }} +@endcomponent diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index e1ea752..ad9b788 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -1,28 +1,4 @@ -@extends('layouts.panel') - -@section('navigation') -
    - -@endsection +@extends('layouts.main') @section('content')
    diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index ae16358..8db925d 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -7,9 +7,15 @@ - {{ config('app.name', 'Laravel') }} + + + + + @@ -30,8 +36,8 @@
    -

    {{ Auth::user()->firstname }}

    - @php($role = Auth::user()->getHighestRole()) - {{ $role->readable_name }} +

    {{ Auth::user()->username() }}

    @@ -107,7 +129,7 @@ - Copyright © 2019 IOByte. All rights reserved. + Copyright © 2019-{{ date('Y') }} IOByte. All rights reserved.
    diff --git a/resources/views/layouts/main.blade.php b/resources/views/layouts/main.blade.php new file mode 100644 index 0000000..da82583 --- /dev/null +++ b/resources/views/layouts/main.blade.php @@ -0,0 +1,27 @@ +@extends('layouts.panel') + +@section('navigation') + + +@endsection diff --git a/resources/views/layouts/profile.blade.php b/resources/views/layouts/profile.blade.php new file mode 100644 index 0000000..d36e980 --- /dev/null +++ b/resources/views/layouts/profile.blade.php @@ -0,0 +1,22 @@ +@extends('layouts.panel') + +@section('navigation') + + +@endsection diff --git a/resources/views/openaudiomc.blade.php b/resources/views/openaudiomc.blade.php new file mode 100644 index 0000000..2e52c37 --- /dev/null +++ b/resources/views/openaudiomc.blade.php @@ -0,0 +1,25 @@ +@extends('layouts.main') + +@section('content') +
    +
    +
    +
    +

    OpenAudioMC

    +
    +
    + @if($type === 1) +

    Welcome to the audio client page! +

    Unfortunately, this server hasn't linked their OpenAudioMc account to the panel yet, but when they do, this page will re-direct you to your web client so you can open it from here with your personal login token. + To get started, login to OpenAudioMc+ using `/oa plus`, go to the "API" tab (as seen in the left side of the page) and copy the url labeled "V1 - Get Token" and paste it into the .env file. Remember to change "<PLAYER UUID>" into "%UUID%" for correct installation.

    + @elseif($type=== 2) +

    Hey there {{ Auth::user()->username() }}! +

    You aren't online in the server right now and thus can't connect to the audio client. Please login or try again in a few seconds.

    + @else +

    You seem to already have an open session with the audio client. You may dismiss this page since you are already connected or try again in a few seconds if you have closed it recently.

    + @endif +
    +
    +
    +
    +@endsection diff --git a/resources/views/order.blade.php b/resources/views/order.blade.php index 171d480..e86e766 100644 --- a/resources/views/order.blade.php +++ b/resources/views/order.blade.php @@ -1,27 +1,4 @@ -@extends('layouts.panel') - -@section('navigation') - - -@endsection +@extends('layouts.main') @section('content')
    @@ -56,7 +33,7 @@ @foreach($dates as $row) @php($time = strtotime($row['date'])) - + @endforeach @else diff --git a/resources/views/panel/home.blade.php b/resources/views/panel/home.blade.php index 91617d8..0e9b287 100644 --- a/resources/views/panel/home.blade.php +++ b/resources/views/panel/home.blade.php @@ -2,49 +2,6 @@ @section('page', 'Dashboard') -@section('navigation') -
  • - - Account - - - -
  • -
  • NAVIGATION
  • -
  • - Dashboard -
  • - @canany(['project_see', 'status_see']) -
  • - - Projects - - -
      - @can('project_see')
    • Project
    • @endcan - @can('order_see')
    • Order
    • @endcan - @can('status_see')
    • Status
    • @endcan -
    -
  • - @endcanany - @canany(['roles_see', 'user_see']) -
  • - - Management - - -
      - @can('user_see')
    • Users
    • @endcan - @can('roles_see')
    • Roles
    • @endcan -
    -
  • - @endcanany -@endsection - @section('content') @if(session('error'))