├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .perlcriticrc ├── .perltidyrc ├── .travis.yml ├── Changes ├── LICENSE ├── MANIFEST ├── MANIFEST.SKIP ├── Makefile.PL ├── README.pod ├── domove ├── README └── localhost │ ├── apache2.4-vhost.conf │ ├── nginx-slovo.conf │ └── public │ ├── css │ └── site.css │ └── img │ └── slovo.svg ├── lib ├── Slovo.pm ├── Slovo │ ├── Cache.pm │ ├── Command.pm │ ├── Command │ │ └── Author │ │ │ ├── generate.pm │ │ │ ├── generate │ │ │ ├── a2htaccess.pm │ │ │ ├── cgi_script.pm │ │ │ └── novy_dom.pm │ │ │ └── inflate.pm │ ├── Controller.pm │ ├── Controller │ │ ├── Auth.pm │ │ ├── Celini.pm │ │ ├── Domove.pm │ │ ├── Example.pm │ │ ├── Groups.pm │ │ ├── Role │ │ │ └── Stranica.pm │ │ ├── Stranici.pm │ │ ├── Upravlenie.pm │ │ └── Users.pm │ ├── Model.pm │ ├── Model │ │ ├── Celini.pm │ │ ├── Domove.pm │ │ ├── Groups.pm │ │ ├── Stranici.pm │ │ └── Users.pm │ ├── Plugin │ │ ├── CGI.pm │ │ ├── DefaultHelpers.pm │ │ ├── MojoDBx.pm │ │ └── TagHelpers.pm │ ├── Task │ │ ├── SendOnboardingEmail.pm │ │ └── SendPasswEmail.pm │ ├── Themes │ │ └── Malka.pm │ ├── Validator.pm │ └── resources │ │ ├── api-v1.0.json │ │ ├── data │ │ ├── migrations.sql │ │ └── used-unicode-symbols.txt │ │ ├── etc │ │ ├── README │ │ ├── routes.conf │ │ └── slovo.conf │ │ ├── public │ │ ├── css │ │ │ ├── fonts.css │ │ │ ├── site.css │ │ │ ├── slovo-min.css │ │ │ └── upravlenie.css │ │ ├── fonts │ │ │ ├── README │ │ │ ├── bukyvede-regular-webfont.woff2 │ │ │ ├── freemono-webfont.woff2 │ │ │ ├── freesans-webfont.woff2 │ │ │ ├── freeserif-webfont.woff2 │ │ │ └── veleka2-regular-01.woff2 │ │ ├── img │ │ │ ├── favicon.ico │ │ │ ├── slovo-white-big.png │ │ │ ├── slovo-white.png │ │ │ ├── slovo-white.svg │ │ │ └── slovo.png │ │ └── js │ │ │ ├── CryptoJS-v3.1.2 │ │ │ └── sha1.js │ │ │ ├── editor.js │ │ │ ├── editormd │ │ │ ├── css │ │ │ │ └── editormd.min.css │ │ │ ├── editormd.min.js │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── editormd-logo.eot │ │ │ │ ├── editormd-logo.svg │ │ │ │ ├── editormd-logo.ttf │ │ │ │ ├── editormd-logo.woff │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── languages │ │ │ │ ├── en.js │ │ │ │ └── zh-tw.js │ │ │ ├── lib │ │ │ │ ├── codemirror │ │ │ │ │ ├── AUTHORS │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── README.md │ │ │ │ │ ├── addons.min.js │ │ │ │ │ ├── codemirror.min.css │ │ │ │ │ ├── codemirror.min.js │ │ │ │ │ └── modes.min.js │ │ │ │ ├── flowchart.min.js │ │ │ │ ├── jquery.flowchart.min.js │ │ │ │ ├── marked.min.js │ │ │ │ ├── plugins │ │ │ │ │ ├── code-block-dialog │ │ │ │ │ │ └── code-block-dialog.js │ │ │ │ │ ├── help-dialog │ │ │ │ │ │ ├── help-dialog.js │ │ │ │ │ │ └── help.md │ │ │ │ │ ├── image-dialog │ │ │ │ │ │ └── image-dialog.js │ │ │ │ │ ├── link-dialog │ │ │ │ │ │ └── link-dialog.js │ │ │ │ │ ├── reference-link-dialog │ │ │ │ │ │ └── reference-link-dialog.js │ │ │ │ │ └── table-dialog │ │ │ │ │ │ └── table-dialog.js │ │ │ │ ├── prettify.min.js │ │ │ │ ├── raphael.min.js │ │ │ │ ├── sequence-diagram.min.js │ │ │ │ └── underscore.min.js │ │ │ └── plugins │ │ │ │ ├── code-block-dialog │ │ │ │ └── code-block-dialog.js │ │ │ │ ├── emoji-dialog │ │ │ │ ├── emoji-dialog.js │ │ │ │ └── emoji.json │ │ │ │ ├── goto-line-dialog │ │ │ │ └── goto-line-dialog.js │ │ │ │ ├── help-dialog │ │ │ │ ├── help-dialog.js │ │ │ │ └── help.md │ │ │ │ ├── html-entities-dialog │ │ │ │ ├── html-entities-dialog.js │ │ │ │ └── html-entities.json │ │ │ │ ├── image-dialog │ │ │ │ └── image-dialog.js │ │ │ │ ├── link-dialog │ │ │ │ └── link-dialog.js │ │ │ │ ├── plugin-template.js │ │ │ │ ├── preformatted-text-dialog │ │ │ │ └── preformatted-text-dialog.js │ │ │ │ ├── reference-link-dialog │ │ │ │ └── reference-link-dialog.js │ │ │ │ ├── table-dialog │ │ │ │ └── table-dialog.js │ │ │ │ └── test-plugin │ │ │ │ └── test-plugin.js │ │ │ ├── mui.min.js │ │ │ ├── sidedrawer.js │ │ │ └── trumbowyg-2.18 │ │ │ ├── langs │ │ │ ├── bg.min.js │ │ │ ├── cs.min.js │ │ │ ├── pl.min.js │ │ │ └── ru.min.js │ │ │ ├── plugins │ │ │ ├── allowtagsfrompaste │ │ │ │ ├── trumbowyg.allowtagsfrompaste.js │ │ │ │ └── trumbowyg.allowtagsfrompaste.min.js │ │ │ ├── base64 │ │ │ │ ├── trumbowyg.base64.js │ │ │ │ └── trumbowyg.base64.min.js │ │ │ └── template │ │ │ │ ├── trumbowyg.template.js │ │ │ │ └── trumbowyg.template.min.js │ │ │ ├── trumbowyg.min.js │ │ │ └── ui │ │ │ ├── icons.svg │ │ │ └── trumbowyg.min.css │ │ └── templates │ │ ├── auth │ │ ├── first_login_form.html.ep │ │ └── lost_password_form.html.ep │ │ ├── celini │ │ ├── _form.html.ep │ │ ├── create.html.ep │ │ ├── edit.html.ep │ │ ├── index.html.ep │ │ └── show.html.ep │ │ ├── domove │ │ ├── _form.html.ep │ │ ├── create.html.ep │ │ ├── edit.html.ep │ │ ├── index.html.ep │ │ └── show.html.ep │ │ ├── example │ │ └── welcome.html.ep │ │ ├── groups │ │ ├── _form.html.ep │ │ ├── create.html.ep │ │ ├── edit.html.ep │ │ ├── index.html.ep │ │ └── show.html.ep │ │ ├── layouts │ │ ├── default.html.ep │ │ ├── podviewer.html.ep │ │ └── upravlenie.html.ep │ │ ├── partials │ │ └── _stranici_index_pid-list.html.ep │ │ ├── stranici │ │ ├── _form.html.ep │ │ ├── create.html.ep │ │ ├── edit.html.ep │ │ ├── index.html.ep │ │ ├── show.html.ep │ │ └── templates │ │ │ └── README │ │ ├── task │ │ ├── send_onboarding_email.txt.ep │ │ └── send_passw_email.txt.ep │ │ ├── upravlenie │ │ └── index.html.ep │ │ └── users │ │ ├── _form.html.ep │ │ ├── create.html.ep │ │ ├── edit.html.ep │ │ ├── index.html.ep │ │ ├── show.html.ep │ │ └── store_result.html.ep └── Test │ └── Mojo │ └── Role │ └── Slovo.pm ├── script ├── slovo ├── slovo.service └── slovo_minion.service └── t ├── 001_load.t ├── 002_basic.t ├── 002_cache.t ├── 002_database.t ├── 003_sign_in.t ├── 004_crud.t ├── 005_site.t ├── 006_api.t ├── 007_domove.t ├── 008_first_login.t ├── 009_cgi_script.t ├── 010_a2htaccess.t ├── 011_novy_dom.t ├── 012_cgi_hosting.t ├── 013_default_helpers.t ├── 014_inflate.t ├── 015_generate_list.t ├── data └── images │ ├── node.png │ ├── perl.gif │ └── perl6.jpeg ├── manifest.t ├── perl-critic.t ├── perltidy.t └── pod.t /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Perl 2 | *.pm linguist-language=Perl 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | workspace 2 | Makefile 3 | Makefile.old 4 | Build 5 | # Files created during the build process 6 | Build.bat 7 | META.* 8 | MYMETA.* 9 | MANIFEST.bak 10 | .build/ 11 | _build/ 12 | cover_db/ 13 | blib/ 14 | inc/ 15 | .lwpcookies 16 | .last_cover_stats 17 | nytprof.out 18 | pod2htm*.tmp 19 | pm_to_blib 20 | Mojolicious-* 21 | Mojolicious-*.tar.gz 22 | 23 | # Avoid some SQLite files 24 | *.db 25 | *.sqlite 26 | *.sqlite-journal 27 | *.sqlite-shm 28 | *.sqlite-wal 29 | 30 | # Files created by IDEs and Editors 31 | *.swp 32 | /*.komodoproject 33 | /tags 34 | .tags* 35 | *.iml 36 | .idea 37 | _Deparsed_XSubs.pm 38 | *.bak 39 | *.sublime-project 40 | *.sublime-workspace 41 | 42 | # Other project specific files 43 | /Slovo-* 44 | /log 45 | /domove/ 46 | /data 47 | /*.conf 48 | 49 | # Locally installed Perl modules 50 | /local 51 | 52 | # Generated files 53 | script/slovo.cgi 54 | -------------------------------------------------------------------------------- /.perlcriticrc: -------------------------------------------------------------------------------- 1 | # Set severity to 'gentle' and then try stern. If we achieve 'harsh' the code is good enough. 2 | #5:gentle;4:stern;3:harsh 3 | severity = harsh 4 | #only = 1 5 | #force = 1 6 | #verbose = 11 7 | top = 50 8 | #theme = (pbp and security and bugs and maintenance and complexity and security) 9 | #include = NamingConventions ClassHierarchies 10 | #exclude = Variables Modules::RequirePackage 11 | #criticism-fatal = 1 12 | #color = 1 13 | #allow-unsafe = 0 14 | pager = less 15 | 16 | # Perl::Critic::Policy::Subroutines::RequireArgUnpacking - Always unpack @_ first. 17 | [Subroutines::RequireArgUnpacking] 18 | short_subroutine_statements = 4 19 | 20 | [Variables::ProhibitPackageVars] 21 | add_packages = Carp 22 | 23 | # Shorten allowed lenght of regexp for more readability. 24 | [RegularExpressions::ProhibitComplexRegexes] 25 | max_characters = 40 26 | 27 | [RegularExpressions::RequireExtendedFormatting] 28 | minimum_regex_length_to_complain_about = 30 29 | 30 | # Temporarily disable check for prototypes, because signatures are recognisided 31 | # as prototypes. See https://github.com/adamkennedy/PPI/issues/194 32 | # Enable again when the above bug is fixed. 33 | [-Subroutines::ProhibitSubroutinePrototypes] 34 | 35 | # Strangely enough, 4 arguments (including $self) in signature are counted as 6 36 | [-Subroutines::ProhibitManyArgs] 37 | 38 | # This policy does not work well with methods and private methods via 'my' 39 | [-Subroutines::ProhibitBuiltinHomonyms] 40 | 41 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | # Mojolicious settings 2 | -pbp # Start with Perl Best Practices 3 | -w # Show all warnings 4 | -iob # Ignore old breakpoints 5 | -l=90 # 90 characters per line 6 | -mbl=2 # No more than 2 blank lines 7 | -i=2 # Indentation is 2 columns 8 | -ci=2 # Continuation indentation is 2 columns 9 | -vt=0 # Less vertical tightness 10 | -pt=2 # High parenthesis tightness 11 | -bt=2 # High brace tightness 12 | -sbt=2 # High square bracket tightness 13 | -isbc # Don't indent comments without leading space 14 | 15 | # Our additional settings 16 | -sac # --stack-all-containers is an abbreviation for -sot -sct. 17 | -wn # --weld-nested-containers 18 | -nst # --nostandard-output 19 | -utf8 # Encoding of both the input and output character streams 20 | -b # Modify files in place 21 | -bext='/' # and delete the originals if there are no error. 22 | -nbl # Opening brace on the same line as the keyword introducing it 23 | --nodelete-semicolons #needed because of a bug in Perl::Critic not recognising 24 | # private subrotines if there is no semicolumn at the closing curly brace. It 25 | # also reports arbitrary nonsense. 26 | 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: perl 2 | dist: xenial 3 | perl: 4 | - "5.30" 5 | - "5.28" 6 | - "5.26" 7 | env: 8 | global: 9 | - "HARNESS_OPTIONS=j9" 10 | - "cpanm -n --installdeps ." 11 | matrix: 12 | #- TEST_AUTHOR=1 13 | - TEST_AUTHOR=0 14 | matrix: 15 | allow_failures: 16 | - TEST_AUTHOR=1 17 | fast_finish: true 18 | notifications: 19 | email: false 20 | 21 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | \bRCS\b 2 | \bCVS\b 3 | \bSCCS\b 4 | ,v$ 5 | \B\.svn\b 6 | \B\.git\b 7 | \B\.gitignore\b 8 | \B\.gitattributes\b 9 | \b_darcs\b 10 | \B\.cvsignore$ 11 | \B\.github\b 12 | 13 | # Avoid VMS specific MakeMaker generated files 14 | \bDescrip.MMS$ 15 | \bDESCRIP.MMS$ 16 | \bdescrip.mms$ 17 | 18 | # Avoid Makemaker generated and utility files. 19 | \bMANIFEST\.bak 20 | \bMakefile$ 21 | \bblib/ 22 | \bMakeMaker-\d 23 | \bpm_to_blib\.ts$ 24 | \bpm_to_blib$ 25 | \bblibdirs\.ts$ # 6.18 through 6.25 generated this 26 | 27 | # Avoid Module::Build generated and utility files. 28 | \bBuild$ 29 | \b_build/ 30 | \bBuild.bat$ 31 | \bBuild.COM$ 32 | \bBUILD.COM$ 33 | \bbuild.com$ 34 | 35 | # Avoid this file 36 | ^MANIFEST\.SKIP 37 | 38 | # Avoid temp, generated and backup files. 39 | ~$ 40 | \.old$ 41 | \#$ 42 | \b\.# 43 | \.bak$ 44 | \.tmp$ 45 | \.# 46 | \.rej$ 47 | ^domove\/localhost\/public\/img/.*?\.(gif|jpe?g|png|ico) 48 | ^domove\/localhost\/public\/cached/.* 49 | ^domove\/localhost\/public\/(js|css) 50 | ^domove\/log 51 | ^domove\/(?!localhost) 52 | ^log\/ 53 | ^data\/ 54 | ^local\/ 55 | ^slovo.conf 56 | ^slovo.+\.conf 57 | ^.+\.cgi 58 | 59 | # Avoid OS-specific files/dirs 60 | # Mac OSX metadata 61 | \B\.DS_Store 62 | # Mac OSX SMB mount metadata files 63 | \B\._ 64 | 65 | # Avoid Devel::Cover and Devel::CoverX::Covered files. 66 | \bcover_db\b 67 | \bcovered\b 68 | 69 | 70 | # Avoid configuration metadata file 71 | ^MYMETA\. 72 | 73 | # Avoid archives and build directories of this distribution 74 | \bSlovo-\d{4}\.\d{2}\.\d{2} 75 | 76 | # Avoid some SQLite files 77 | \b.+\.db 78 | \b.+\.sqlite 79 | .+\.sqlite-journal$ 80 | .+\.sqlite-shm$ 81 | .+\.sqlite-wal$ 82 | 83 | 84 | # Avoid files created by some IDEs, editors and tools 85 | \.komodoproject$ 86 | \.swp$ 87 | tags$ 88 | \.tags 89 | .idea 90 | .iml 91 | _Deparsed_XSubs.pm$ 92 | 93 | # Avoid CI tools files 94 | \.travis.yml 95 | .+\.tdy 96 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | package Slovo; 2 | use 5.026000; 3 | use ExtUtils::MakeMaker 7.24; 4 | use strict; 5 | use warnings; 6 | use utf8; 7 | 8 | # See lib/ExtUtils/MakeMaker.pm for details of how to influence 9 | # the contents of the Makefile that is written. 10 | my $module_file = 'lib/' . __PACKAGE__ . '.pm'; 11 | my $git_url = 'https://github.com/kberov/' . __PACKAGE__; 12 | my $PREREQ_PM = { 13 | 'Authen::SASL' => '2.16', 14 | 'Class::Method::Modifiers' => '2.13', 15 | 'Cpanel::JSON::XS' => '4.32', 16 | 'Minion::Backend::SQLite' => '5.0.7', 17 | 'Mojo::SQLite' => '3.009', 18 | 'Mojolicious' => '9.28', 19 | 'Mojolicious::Plugin::PODViewer' => '0.007', 20 | 'Mojolicious::Plugin::Authentication' => '1.39', 21 | 'Mojolicious::Plugin::OpenAPI' => '5.07', 22 | 'Mojolicious::Plugin::RoutesConfig' => '0.07', 23 | 'Net::SMTP' => '3.14', 24 | 'Role::Tiny' => '2.002004', 25 | 'EV' => '4.33' 26 | }; 27 | 28 | if ($ENV{TEST_AUTHOR}) { 29 | $PREREQ_PM = { 30 | %$PREREQ_PM, 31 | 'Test::Pod' => '1.52', 32 | 'Test::Perl::Critic' => '1.04', 33 | 'Perl::Critic' => '1.140', 34 | 'Test::PerlTidy' => '20220902' 35 | }; 36 | } 37 | 38 | WriteMakefile( 39 | NAME => __PACKAGE__, 40 | VERSION_FROM => $module_file, 41 | AUTHOR => 'Красимир Беров (berov@cpan.org)', 42 | ABSTRACT_FROM => $module_file, 43 | LICENSE => 'artistic_2', 44 | PREREQ_PM => $PREREQ_PM, 45 | CONFIGURE_REQUIRES => {}, 46 | BUILD_REQUIRES => {'ExtUtils::MakeMaker' => '7.24'}, 47 | TEST_REQUIRES => {}, 48 | test => {TESTS => 't/*.t'}, 49 | EXE_FILES => ['script/slovo'], 50 | clean => {FILES => '*.conf Slovo-* lib/Slovo/resources/data/*.sqli*'}, 51 | MIN_PERL_VERSION => '5.026000', 52 | META_MERGE => { 53 | dynamic_config => 0, 54 | 'meta-spec' => {version => 2}, 55 | no_index => {directory => ['t']}, 56 | prereqs => {runtime => {requires => {perl => '5.026000'}}}, 57 | resources => { 58 | bugtracker => {web => "$git_url/issues"}, 59 | homepage => $git_url, 60 | license => ['http://www.opensource.org/licenses/artistic-license-2.0'], 61 | repository => {type => 'git', url => "$git_url.git", web => $git_url,}, 62 | }, 63 | }, 64 | ); 65 | 66 | 67 | sub MY::postamble { 68 | my $preop = qq 'podselect $module_file > README.pod;'; 69 | my @perltidy_files = qw(script/slovo Makefile.PL); 70 | my $options = { 71 | no_chdir => 1, 72 | wanted => sub { 73 | push @perltidy_files, $_ if $_ =~ /\.(PL|pm|pl|t|conf)$/; 74 | } 75 | }; 76 | File::Find::find($options, 'lib', 't'); 77 | my $perltidy_files = join '\\' . $/ . "\t ", @perltidy_files; 78 | return <<"TARGETS"; 79 | readme :: 80 | \t$preop 81 | dist : readme 82 | 83 | perltidy :: 84 | \tperltidy -pro=.perltidyrc \\ 85 | \t$perltidy_files 86 | 87 | TARGETS 88 | } 89 | 90 | __END__ 91 | 92 | =encoding utf8 93 | 94 | =head1 NAME 95 | 96 | Makefile.PL for the Slovo project 97 | 98 | =head1 SYNOPSIS 99 | 100 | Some commands: 101 | 102 | Set C, remove old Slovo installation, make, test, install, create 103 | data directory for sqlite database and run slovo to see available commands. 104 | 105 | INSTALL_BASE=~/opt/slovo && rm -rf $INSTALL_BASE && make distclean; \ 106 | perl Makefile.PL INSTALL_BASE=$INSTALL_BASE && make && make test && make install \ 107 | && $INSTALL_BASE/bin/slovo eval 'app->home->child("data")->make_path({mode => 0700});' \ 108 | && $INSTALL_BASE/bin/slovo 109 | 110 | Use cpanm to install or update into a custom location as self contained application and 111 | run slovo to see how it's going 112 | 113 | # From metacpan. org 114 | export PREFIX=~/opt/slovo; 115 | cpanm -M https://cpan.metacpan.org -n --self-contained -l $PREFIX Slovo \ 116 | $PREFIX/bin/slovo eval 'app->home->child("data")->make_path({mode => 0700});' \ 117 | $PREFIX/bin/slovo 118 | 119 | # From the directory where you unpacked Slovo 120 | export PREFIX=~/opt/slovo; 121 | cpanm . -n --self-contained -l $PREFIX Slovo 122 | $PREFIX/bin/slovo eval 'app->home->child("data")->make_path({mode => 0700});' 123 | $PREFIX/bin/slovo 124 | 125 | Start the development server and open a browser 126 | 127 | morbo ./script/slovo -l http://*:3000 & sleep 1 exo-open http://localhost:3000 128 | 129 | Start the production server 130 | 131 | hypnotoad script/slovo 132 | # you will see something like the following: 133 | [2019-02-24 19:38:08.69754] [13570] [info] Listening at "http://127.0.0.1:9090" 134 | Server available at http://127.0.0.1:9090 135 | [2019-02-24 19:38:08.69804] [13570] [info] Listening at "http://[::1]:9090" 136 | Server available at http://[::1]:9090 137 | 138 | 139 | Build Makefile even directly in vim 140 | 141 | !perl Makefile.PL 142 | 143 | When you want to add new files to the ditribution 144 | 145 | make manifest 146 | 147 | Beautify your code 148 | 149 | make perltidy 150 | 151 | Re-generate README and README.pod 152 | 153 | make readme 154 | 155 | =cut 156 | 157 | -------------------------------------------------------------------------------- /domove/README: -------------------------------------------------------------------------------- 1 | This is the default folder for domains and their assets. Each domain can have 2 | its own templates and static assets like CSS, JS, images. 3 | -------------------------------------------------------------------------------- /domove/localhost/apache2.4-vhost.conf: -------------------------------------------------------------------------------- 1 | # Modify this file accordingly to 2 | # lib/Slovo/resources/etc/slovo.conf and include it in Apache config. 3 | # SLOVO_DOMOVE is $MOJO_HOME/domove 4 | # Define SLOVO_DOMOVE "/home/${USER}/opt/slovo/domove" 5 | # Include "${SLOVO_DOMOVE}/localhost/apache2.4-vhost.conf" 6 | 7 | Listen 3001 8 | 9 | ServerAdmin webmaster@bg.localhost 10 | DocumentRoot "${SLOVO_DOMOVE}/localhost/public" 11 | ServerName bg.localhost 12 | ServerAlias en.localhost 13 | # See http://localhost:8080/manual/mod/core.html#loglevel 14 | # Log level keyword must be one of emerg/alert/crit/error/warn/notice/info/debug/trace1/.../trace8 15 | LogLevel info rewrite:trace2 proxy:trace1 16 | ErrorLog "${SLOVO_DOMOVE}/log/localhost-error_log" 17 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" common 18 | CustomLog "${SLOVO_DOMOVE}/log/localhost-access_log" common 19 | 20 | Options +Indexes +FollowSymLinks -ExecCGI 21 | #AllowOverride All 22 | AllowOverride None 23 | #see http://httpd.apache.org/docs/2.4/upgrading.html 24 | #2.4 configuration: 25 | 26 | Require all granted 27 | 28 | 29 | ProxyRequests Off 30 | # Needed to recognise and serve content for the right domain. 31 | # Crucial for multidomain support 32 | ProxyPreserveHost On 33 | ProxyPass / http://127.0.0.1:9090/ keepalive=On 34 | ProxyPassReverse / http://127.0.0.1:9090/ 35 | RequestHeader set X-Forwarded-Proto "http" 36 | 37 | 38 | -------------------------------------------------------------------------------- /domove/localhost/nginx-slovo.conf: -------------------------------------------------------------------------------- 1 | upstream mojo { 2 | server 127.0.0.1:3000; 3 | } 4 | 5 | server { 6 | listen 443; 7 | server_name bg.localhost en.localhost; 8 | 9 | ssl on; 10 | 11 | # set the correct paths 12 | ssl_certificate /path/to/letsencrypt/fullchain.pem; 13 | ssl_certificate_key /path/to/letsencrypt/privkey.pem; 14 | 15 | location / { 16 | proxy_pass http://mojo/; 17 | proxy_http_version 1.1; 18 | proxy_set_header Upgrade $http_upgrade; 19 | proxy_set_header Connection "upgrade"; 20 | proxy_set_header Host $host; 21 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 22 | proxy_set_header X-Forwarded-Proto $scheme; 23 | proxy_set_header X-Real-IP $remote_addr; 24 | proxy_set_header X-SSL-User $ssl_client_s_dn; 25 | proxy_set_header X-SSL-Issuer $ssl_client_i_dn; 26 | proxy_set_header X-SSL-ServerName $ssl_server_name; 27 | proxy_set_header X-SSL-Verify $ssl_client_verify; 28 | } 29 | } 30 | 31 | server { 32 | listen 80; 33 | server_name bg.localhost en.localhost; 34 | 35 | location / { 36 | proxy_pass http://mojo/; 37 | proxy_http_version 1.1; 38 | proxy_set_header Upgrade $http_upgrade; 39 | proxy_set_header Connection "upgrade"; 40 | proxy_set_header Host $host; 41 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 42 | proxy_set_header X-Forwarded-Proto $scheme; 43 | proxy_set_header X-Real-IP $remote_addr; 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /domove/localhost/public/img/slovo.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 17 | 22 | 28 | 33 | 38 | 44 | 45 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 61 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/Slovo/Cache.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Cache; 2 | use Mojo::Base 'Mojo::Cache'; 3 | 4 | has cache => sub { {} }; 5 | has key_prefix => ''; 6 | has max_keys => 111; 7 | 8 | sub new { 9 | my $self = shift->SUPER::new(@_); 10 | $self->{key_prefix} //= ''; 11 | $self->{cache} ||= {}; 12 | $self->{queue} ||= []; 13 | return $self; 14 | } 15 | 16 | ## no critic qw(RequireArgUnpacking RequireFinalReturn) 17 | sub get { $_[0]->{cache}{$_[0]->{key_prefix} . ($_[1] // '')} } 18 | 19 | ## no critic qw(ProhibitAmbiguousNames) 20 | sub set { 21 | my ($self, $key, $value) = @_; 22 | $key = $_[0]->{key_prefix} . $key; 23 | return $self if not((my $max = $self->max_keys) > 0); 24 | 25 | my $cache = $self->{cache}; 26 | my $queue = $self->{queue}; 27 | delete $cache->{shift @$queue} while @$queue >= $max; 28 | push @$queue, $key unless exists $cache->{$key}; 29 | $cache->{$key} = $value; 30 | 31 | return $self; 32 | } 33 | 34 | 1; 35 | 36 | =encoding utf8 37 | 38 | =head1 NAME 39 | 40 | Slovo::Cache - Naive in-memory cache 41 | 42 | =head1 SYNOPSIS 43 | 44 | use Slovo::Cache; 45 | 46 | my $cache = Slovo::Cache->new(max_keys => 50, prefix=>'baz'); 47 | $cache->set(foo => 'bar'); 48 | my $foo = $cache->get('foo'); # bar 49 | 50 | =head1 DESCRIPTION 51 | 52 | L is a naive in-memory cache with size limits and key prefixes. 53 | It is a modification of L. It can set a prefix to the keys. This 54 | is how we cache templates to support different themes per domain. 55 | 56 | =head1 ATTRIBUTES 57 | 58 | L implements the following attributes. 59 | 60 | =head2 key_prefix 61 | 62 | The prefix which will be used for each key. Defaults to empty string. It is 63 | reset in L to set different namespace for cached 64 | templates per domain. 65 | 66 | =head2 max_keys 67 | 68 | my $max = $cache->max_keys; 69 | $cache = $cache->max_keys(50); 70 | 71 | Maximum number of cache keys, defaults to C<111>. Setting the value to C<0> will disable caching. 72 | 73 | =head1 METHODS 74 | 75 | L inherits all methods from L and implements the following new ones. 76 | 77 | =head2 get 78 | 79 | my $value = $cache->get('foo'); 80 | 81 | Get cached value. 82 | 83 | =head2 set 84 | 85 | $cache = $cache->set(foo => 'bar'); 86 | 87 | Set cached value. 88 | 89 | =head1 SEE ALSO 90 | 91 | L, 92 | L, L, L. 93 | 94 | =cut 95 | -------------------------------------------------------------------------------- /lib/Slovo/Command.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Command; 2 | use Mojo::Base 'Mojolicious::Command'; 3 | 4 | 1; 5 | 6 | =encoding utf8 7 | 8 | =head1 NAME 9 | 10 | Slovo::Command - Slovo Command base class 11 | 12 | =head1 DESCRIPTION 13 | 14 | L is an abstract base class for L commands. For now we 15 | just use it as namespace. Slovo::Command ISA L. 16 | 17 | 18 | =head1 SEE ALSO 19 | 20 | See L for a list of commands that are 21 | available by default in Mojolicious. 22 | 23 | L 24 | 25 | =cut 26 | 27 | -------------------------------------------------------------------------------- /lib/Slovo/Command/Author/generate.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Command::Author::generate; 2 | use Mojo::Base 'Mojolicious::Command::Author::generate'; 3 | 4 | has hint => <<'EOF'; 5 | 6 | See 'slovo generate help GENERATOR' for more information on a specific 7 | generator. 8 | EOF 9 | has message => sub { shift->extract_usage . "\nGenerators:\n" }; 10 | has namespaces => sub { [__PACKAGE__, 'Mojolicious::Command::Author::generate'] }; 11 | 12 | 1; 13 | 14 | =encoding utf8 15 | 16 | =head1 NAME 17 | 18 | Slovo::Command::Author::generate - Generator command 19 | 20 | =head1 SYNOPSIS 21 | 22 | Usage: slovo generate GENERATOR [OPTIONS] 23 | 24 | Example: slovo generate cgi_script -f index.cgi --cgi_mode productuion 25 | 26 | =head1 DESCRIPTION 27 | 28 | L lists available generators. 29 | 30 | This is a core command, that means it is always enabled and its code a good 31 | example for learning to build new commands, you're welcome to fork it. 32 | 33 | See L for a list of commands that are 34 | available by default in Mojolicious. 35 | 36 | =head1 ATTRIBUTES 37 | 38 | L inherits all attributes from 39 | L and implements the following new 40 | ones. 41 | 42 | =head2 description 43 | 44 | my $description = $generator->description; 45 | $generator = $generator->description('Foo'); 46 | 47 | Short description of this command, used for the command list. 48 | 49 | =head2 hint 50 | 51 | my $hint = $generator->hint; 52 | $generator = $generator->hint('Foo'); 53 | 54 | Short hint shown after listing available generator commands. 55 | 56 | =head2 message 57 | 58 | my $msg = $generator->message; 59 | $generator = $generator->message('Bar'); 60 | 61 | Short usage message shown before listing available generator commands. 62 | 63 | =head2 namespaces 64 | 65 | my $namespaces = $generator->namespaces; 66 | $generator = $generator->namespaces(['MyApp::Command::generate']); 67 | 68 | Namespaces to search for available generator commands, defaults to 69 | L. 70 | 71 | =head1 METHODS 72 | 73 | L inherits all methods from 74 | L and implements the following new ones. 75 | 76 | =head2 help 77 | 78 | $generator->help('app'); 79 | 80 | Print usage information for generator command. 81 | 82 | =head1 SEE ALSO 83 | 84 | L, L, L. 85 | 86 | =cut 87 | -------------------------------------------------------------------------------- /lib/Slovo/Command/Author/generate/cgi_script.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Command::Author::generate::cgi_script; 2 | use Mojo::Base 'Slovo::Command', -signatures; 3 | 4 | use Mojo::File 'path'; 5 | use Mojo::Util 'getopt'; 6 | use Config; 7 | 8 | has description => 'Generate a CGI script for running Slovo under Apache 2/CGI'; 9 | has usage => sub { shift->extract_usage }; 10 | has exe => sub { 11 | my $app = shift->app; 12 | -f $app->home->child('bin/' . $app->moniker) 13 | ? $app->home->child('bin/' . $app->moniker) 14 | : $app->home->child('script/' . $app->moniker); 15 | 16 | }; 17 | 18 | sub run ($self, @args) { 19 | getopt \@args, 20 | 21 | 'f|filename=s' => \(my $filename = ''), 22 | 'c|cgi_mode=s' => \(my $mode = ''); 23 | my $app = $self->app; 24 | 25 | unless ($filename) { 26 | $filename = $app->moniker . '.cgi'; 27 | say 'Assuming script name: ' . $filename; 28 | } 29 | unless ($mode) { 30 | $mode = $app->mode; 31 | say 'Assuming mode: ' . $mode; 32 | } 33 | my $file = $app->home->rel_file($filename)->to_abs; 34 | $self->render_to_file('cgi_script', $file, 35 | {app => $app, exe => $self->exe, mode => $mode, perlpath => $Config{perlpath}}); 36 | $self->chmod_file($file, oct(755)); 37 | return; 38 | } 39 | 40 | 1; 41 | 42 | 43 | =encoding utf8 44 | 45 | =head1 NAME 46 | 47 | Slovo::Command::Author::generate::cgi_script - Generate a CGI script for 48 | running Slovo under Apache 2/CGI 49 | 50 | =head1 SYNOPSIS 51 | 52 | Usage: slovo [OPTIONS] 53 | # Default values. 54 | slovo generate cgi_script 55 | # Custom values 56 | slovo generate cgi_script -f slovo.cgi -m production 57 | 58 | Options: 59 | -h, --help Show this summary of available options 60 | -f, --filename Defaults to $app->moniker.cgi 61 | -c, --cgi_mode Defaults to current $app->mode 62 | -m, --mode Operating mode for your application, defaults to the 63 | value of MOJO_MODE/PLACK_ENV or "development". Will 64 | be used for --cgi_mode. 65 | 66 | =head1 DESCRIPTION 67 | 68 | After running this command it is expected usually to run also 69 | L to generate the C<.htaccess> 70 | file for the set of domains which you will be running on this virtual host. 71 | 72 | L will generate a CGI script for 73 | running Slovo under Apache2/CGI. Although Slovo performs best as a daemon, run 74 | by hypnotoad, it can as well be used on a cheap shared hosting. When the 75 | produced CGI script (e.g. C) is run on a page from the site, it 76 | will dump the produced on-the-fly HTML to a static html-file. Later, upon 77 | another HTTP request, the produced html-file will be just spit out by Apache 78 | without invoking slovo.cgi again. This way Slovo acts as a static site 79 | generator. This is completely enough for bloggers. Serving static pages is 80 | faster than anything else. 81 | 82 | =head1 ATTRIBUTES 83 | 84 | L inherits all attributes from 85 | L and implements the following new ones. 86 | 87 | =head2 description 88 | 89 | my $description = $cgi_script->description; 90 | $cpanify = $cgi_script->description('Foo'); 91 | 92 | Short description of this command, used for the command list. 93 | 94 | =head2 usage 95 | 96 | my $usage = $cgi_script->usage; 97 | $cpanify = $cgi_script->usage('Foo'); 98 | 99 | Usage information for this command, used for the help screen. 100 | 101 | =head1 METHODS 102 | 103 | L inherits all methods from 104 | L and implements the following new ones. 105 | 106 | =head2 run 107 | 108 | $cgi_script->run(@ARGV); 109 | 110 | Run this command. 111 | 112 | =head1 SEE ALSO 113 | 114 | L, 115 | L,L 116 | L, 117 | L, L. 118 | 119 | =cut 120 | 121 | __DATA__ 122 | 123 | @@ cgi_script 124 | #!<%=$perlpath%> 125 | use strict; 126 | use warnings; 127 | use lib (); 128 | 129 | BEGIN { 130 | $ENV{MOJO_MODE} = $ENV{HTTP_MOJO_MODE} || '<%=$mode%>'; 131 | $ENV{MOJO_HOME} = $ENV{HTTP_MOJO_HOME} || '<%=$app->home%>'; 132 | for ( 133 | "$ENV{MOJO_HOME}/local/lib/perl5", "$ENV{MOJO_HOME}/lib/perl5", 134 | "$ENV{MOJO_HOME}/lib", "$ENV{MOJO_HOME}/site/lib" 135 | ) 136 | { 137 | lib->import($_) if (-d $_); 138 | } 139 | } 140 | 141 | use Mojolicious::Commands; 142 | 143 | # Start Slovo as CGI 144 | Mojolicious::Commands->start_app('Slovo', 'cgi'); 145 | 146 | -------------------------------------------------------------------------------- /lib/Slovo/Controller.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Controller; 2 | use Mojo::Base 'Mojolicious::Controller', -signatures; 3 | 4 | has not_found_id => sub { $_[0]->stranici->not_found_id }; 5 | has not_found_code => 404; 6 | 7 | has description => 'Slovo is a simple extensible CMS.'; 8 | has keywords => 'SSOT, CRM, ERP, CMS, Perl, Mojolicious, SQL'; 9 | 10 | sub generator { return 'Slovo ' . $Slovo::VERSION . ' - ' . $Slovo::CODENAME } 11 | 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/Slovo/Controller/Domove.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Controller::Domove; 2 | use Mojo::Base 'Slovo::Controller', -signatures; 3 | 4 | # GET /domove/create 5 | # Display form for creating resource in table domove. 6 | sub create ($c) { 7 | return $c->render(in => {}); 8 | } 9 | 10 | # POST /domove 11 | # Add a new record to table domove. 12 | sub store ($c) { 13 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 14 | $c->openapi->valid_input or return; 15 | my $in = $c->validation->output; 16 | my $id = $c->domove->add($in); 17 | $c->res->headers->location($c->url_for("api.show_domove", id => $id)->to_string); 18 | return $c->render(openapi => '', status => 201); 19 | } 20 | 21 | # 1. Validate input 22 | my $v = $c->_validation; 23 | return $c->render(action => 'create', in => {}) if $v->has_error; 24 | 25 | # 2. Insert it into the database 26 | my $id = $c->domove->add($v->output); 27 | 28 | # 3. Prepare the response data or just return "201 Created" 29 | # See https://developer.mozilla.org/docs/Web/HTTP/Status/201 30 | return $c->redirect_to('show_domove', id => $id); 31 | } 32 | 33 | # GET /domove/:id/edit 34 | # Display form for edititing resource in table domove. 35 | sub edit ($c) { 36 | my $row = $c->domove->find($c->param('id')); 37 | $c->req->param($_ => $row->{$_}) for keys %$row; # prefill form fields. 38 | return $c->render(in => $row); 39 | } 40 | 41 | # PUT /domove/:id 42 | # Update the record in table domove 43 | sub update ($c) { 44 | 45 | # Validate input 46 | my $v = $c->_validation; 47 | return $c->render(action => 'edit') if $v->has_error; 48 | 49 | # Update the record 50 | my $id = $c->param('id'); 51 | $c->domove->save($id, $v->output); 52 | 53 | # Redirect to the updated record or just send "204 No Content" 54 | # See https://developer.mozilla.org/docs/Web/HTTP/Status/204 55 | return $c->redirect_to('show_domove', id => $id); 56 | } 57 | 58 | # GET /domove/:id 59 | # Display a record from table domove. 60 | sub show ($c) { 61 | my $id = $c->param('id'); 62 | my $row = $c->domove->find($id); 63 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 64 | return $c->render( 65 | openapi => {errors => [{path => $c->url_for, message => 'Not Found'}]}, 66 | status => 404 67 | ) unless $row; 68 | return $c->render(openapi => $row); 69 | } 70 | return $c->render(text => $c->res->default_message(404), status => 404) unless $row; 71 | return $c->render(dom => $row); 72 | } 73 | 74 | # GET /domove 75 | # List resources from table domove. 76 | ## no critic qw(Subroutines::ProhibitBuiltinHomonyms) 77 | sub index ($c) { 78 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 79 | $c->openapi->valid_input or return; 80 | my $input = $c->validation->output; 81 | return $c->render(openapi => $c->domove->all($input)); 82 | } 83 | return $c->render(domove => $c->domove->all); 84 | } 85 | 86 | # DELETE /domove/:id 87 | sub remove ($c) { 88 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 89 | $c->openapi->valid_input or return; 90 | my $input = $c->validation->output; 91 | my $row = $c->domove->find($input->{id}); 92 | $c->render( 93 | openapi => {errors => [{path => $c->url_for, message => 'Not Found'}]}, 94 | status => 404 95 | ) 96 | && return 97 | unless $row; 98 | $c->domove->remove($input->{id}); 99 | return $c->render(openapi => '', status => 204); 100 | } 101 | $c->domove->remove($c->param('id')); 102 | return $c->redirect_to('home_domove'); 103 | } 104 | 105 | 106 | # Validation for actions that store or update 107 | sub _validation ($c) { 108 | my $v = $c->validation; 109 | 110 | # Add validation rules for the record to be stored in the database 111 | $v->required('domain', 'trim')->size(0, 63); 112 | $v->optional('aliases', 'trim')->like(qr/[a-z0-9\-\.\s]{1,2000}/); 113 | $v->required('site_name', 'trim')->size(0, 63); 114 | $v->required('description', 'trim')->size(0, 2000); 115 | $v->optional('owner_id', 'trim')->like(qr/^\d+$/a); 116 | $v->optional('group_id', 'trim')->like(qr/^\d+$/a); 117 | $v->optional('permissions', 'trim')->like(qr/^[dlrwx\-]{10}$/); 118 | $v->optional('templates', 'trim',)->like(qr/^[\w\/]{0,255}$/); 119 | $v->required('published', 'trim')->like(qr/^[0-2]$/); 120 | 121 | return $v; 122 | } 123 | 124 | 1; 125 | -------------------------------------------------------------------------------- /lib/Slovo/Controller/Example.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Controller::Example; 2 | use Mojo::Base 'Slovo::Controller'; 3 | 4 | # This action will render a template 5 | sub welcome { 6 | my $self = shift; 7 | my $msg = 'Добре дошли в приложението „Слово“!'; 8 | 9 | # Render template "example/welcome.html.ep" with message 10 | return $self->render(msg => $msg); 11 | } 12 | 13 | 1; 14 | -------------------------------------------------------------------------------- /lib/Slovo/Controller/Groups.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Controller::Groups; 2 | use Mojo::Base 'Slovo::Controller', -signatures; 3 | 4 | # GET /groups/create 5 | # Display form for creating resource in table groups. 6 | sub create ($c) { 7 | return $c->render(groups => {}); 8 | } 9 | 10 | # POST /groups 11 | # Add a new record to table groups. 12 | sub store ($c) { 13 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 14 | $c->openapi->valid_input or return; 15 | my $in = $c->validation->output; 16 | my $id = $c->groups->add($in); 17 | $c->res->headers->location($c->url_for("api.show_groups", id => $id)->to_string); 18 | return $c->render(openapi => '', status => 201); 19 | } 20 | 21 | # 1. Validate input 22 | my $validation = $c->_validation; 23 | return $c->render(action => 'create', groups => {}) if $validation->has_error; 24 | 25 | # 2. Insert it into the database 26 | my $id = $c->groups->add($validation->output); 27 | 28 | # 3. Prepare the response data or just return "201 Created" 29 | # See https://developer.mozilla.org/docs/Web/HTTP/Status/201 30 | return $c->redirect_to('show_groups', id => $id); 31 | } 32 | 33 | # GET /groups/:id/edit 34 | # Display form for edititing resource in table groups. 35 | sub edit ($c) { 36 | return $c->render(groups => $c->groups->find($c->param('id'))); 37 | } 38 | 39 | # PUT /groups/:id 40 | # Update the record in table groups 41 | sub update ($c) { 42 | 43 | # Validate input 44 | my $validation = $c->_validation; 45 | return $c->render(action => 'edit', groups => {}) if $validation->has_error; 46 | 47 | # Update the record 48 | my $id = $c->param('id'); 49 | $c->groups->save($id, $validation->output); 50 | 51 | # Redirect to the updated record or just send "204 No Content" 52 | # See https://developer.mozilla.org/docs/Web/HTTP/Status/204 53 | return $c->redirect_to('show_groups', id => $id); 54 | } 55 | 56 | # GET /groups/:id 57 | # Display a record from table groups. 58 | sub show ($c) { 59 | my $row = $c->groups->find($c->param('id')); 60 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 61 | return $c->render( 62 | openapi => {errors => [{path => $c->url_for, message => 'Not Found'}]}, 63 | status => 404 64 | ) unless $row; 65 | return $c->render(openapi => $row); 66 | } 67 | $row = $c->groups->find($c->param('id')); 68 | return $c->render(text => $c->res->default_message(404), status => 404) unless $row; 69 | return $c->render(groups => $row); 70 | } 71 | 72 | # GET /groups 73 | # List resources from table groups. 74 | ## no critic qw(Subroutines::ProhibitBuiltinHomonyms) 75 | sub index ($c) { 76 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 77 | $c->openapi->valid_input or return; 78 | my $input = $c->validation->output; 79 | return $c->render(openapi => $c->groups->all($input)); 80 | } 81 | return $c->render(groups => $c->groups->all); 82 | } 83 | 84 | # DELETE /groups/:id 85 | sub remove ($c) { 86 | if ($c->current_route =~ /^api\./) { #invoked via OpenAPI 87 | $c->openapi->valid_input or return; 88 | my $input = $c->validation->output; 89 | my $row = $c->groups->find($input->{id}); 90 | $c->render( 91 | openapi => {errors => [{path => $c->url_for, message => 'Not Found'}]}, 92 | status => 404 93 | ) 94 | && return 95 | unless $row; 96 | $c->groups->remove($input->{id}); 97 | return $c->render(openapi => '', status => 204); 98 | } 99 | $c->groups->remove($c->param('id')); 100 | return $c->redirect_to('home_groups'); 101 | } 102 | 103 | 104 | # Validation for actions that store or update 105 | sub _validation ($c) { 106 | my $v = $c->validation; 107 | 108 | # Add validation rules for the record to be stored in the database 109 | $v->required('id') if $c->stash->{action} ne 'store'; 110 | $v->required('name', 'trim')->size(0, 100); 111 | $v->required('description', 'trim')->size(0, 255); 112 | $v->optional('created_by', 'trim')->like(qr/^\d+$/); 113 | $v->optional('changed_by', 'trim')->like(qr/^\d+$/); 114 | $v->required('disabled', 'trim')->like(qr/^\d$/); 115 | 116 | return $v; 117 | } 118 | 119 | 1; 120 | -------------------------------------------------------------------------------- /lib/Slovo/Controller/Upravlenie.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Controller::Upravlenie; 2 | use Mojo::Base 'Slovo::Controller', -signatures; 3 | 4 | # ANY /manage/ 5 | ## no critic qw(Subroutines::ProhibitBuiltinHomonyms) 6 | sub index ($c) { 7 | state $menu = [qw(minion groups users domove stranici celini)]; 8 | $c->stash->{menu} = [ 9 | map { 10 | $_ =~ m'groups|minion|domove' && !$c->groups->is_admin($c->user->{id}) ? () : $_ 11 | } @$menu 12 | ]; 13 | return $c->render(); 14 | } 15 | 16 | 1; 17 | 18 | =encoding utf8 19 | 20 | =head1 NAME 21 | 22 | Slovo::Controller::Upravlenie - the management dashboard 23 | 24 | =head1 DESCRIPTION 25 | 26 | Slovo::Controller::Upravlenie inherits all methods from L and implements the following. 27 | 28 | 29 | =head1 ACTIONS 30 | 31 | Slovo::Controller::Upravlenie implements the following actions C '/manage'>. 32 | 33 | =head2 index 34 | 35 | Route: C<{any => '/', to => 'upravlenie#index', name => 'home_upravlenie'}> 36 | 37 | Displays the main page C '/manage'>. 38 | 39 | 40 | =head1 SEE ALSO 41 | 42 | L 43 | 44 | =cut 45 | -------------------------------------------------------------------------------- /lib/Slovo/Model.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Model; 2 | use Mojo::Base -base, -signatures; 3 | 4 | has 'dbx'; 5 | has c => sub { Slovo::Controller->new() }; 6 | 7 | sub table { 8 | Carp::croak "Method not implemented. Please implement it in ${\ ref($_[0])}."; 9 | } 10 | 11 | sub all ($self, $opts = {}) { 12 | $opts->{limit} //= 100; 13 | $opts->{limit} = 100 unless $opts->{limit} =~ /^\d+$/; 14 | $opts->{offset} //= 0; 15 | $opts->{offset} = 0 unless $opts->{offset} =~ /^\d+$/; 16 | $opts->{where} //= {}; 17 | my $table = $opts->{table} || $self->table; 18 | state $abstr = $self->dbx->abstract; 19 | my ($sql, @bind) 20 | = $abstr->select($table, $opts->{columns}, $opts->{where}, $opts->{order_by}); 21 | $sql .= " LIMIT $opts->{limit}" . ($opts->{offset} ? " OFFSET $opts->{offset}" : ''); 22 | 23 | # local $self->dbx->db->dbh->{TraceLevel} = "3|SQL"; 24 | return 25 | eval { $self->dbx->db->query($sql, @bind)->hashes } 26 | || Carp::croak("Wrong SQL:$sql\n or bind values: @bind\n$@"); 27 | } 28 | 29 | # similar to all but retuns only one row 30 | sub one ($m, $o) { 31 | return $m->dbx->db->select($m->table, $o->{columns}, $o->{where})->hash; 32 | } 33 | 34 | #update a record 35 | sub save ($self, $id, $row) { 36 | 37 | # local $self->dbx->db->dbh->{TraceLevel} = "3|SQL"; 38 | return $self->dbx->db->update($self->table, $row, {id => $id}); 39 | } 40 | 41 | sub find_where ($m, $where = {}) { 42 | 43 | # local $m->dbx->db->dbh->{TraceLevel} = "3|SQL"; 44 | state $abstr = $m->dbx->abstract; 45 | if (ref $where eq 'HASH' ? keys %$where : @$where) { 46 | my ($sql, @bind) = $abstr->where($where); 47 | return $m->dbx->db->query("SELECT * FROM ${\ $m->table } $sql LIMIT 1", @bind)->hash; 48 | } 49 | return; 50 | } 51 | 52 | # Find by ID. 53 | sub find { 54 | return $_[0]->dbx->db->select($_[0]->table, undef, {id => $_[1]})->hash; 55 | } 56 | 57 | 58 | sub remove ($m, $id) { 59 | my $db = $m->dbx->db; 60 | my $table = $m->table; 61 | return eval { 62 | my $tx = $db->begin; 63 | $m->remove_aliases($db, $id, $table); 64 | $db->delete($table, {id => $id}); 65 | $tx->commit; 66 | } || Carp::croak("Error deleting record from $table: $@"); 67 | } 68 | 69 | sub add { 70 | 71 | # local $_[0]->dbx->db->dbh->{TraceLevel} = "3|SQL"; 72 | return $_[0]->dbx->db->insert($_[0]->table, $_[1])->last_insert_id; 73 | } 74 | 75 | # Returns hashref for where clause where permissions allow the user to read 76 | # records. 77 | sub readable_by ($self, $user) { 78 | my $t = $self->table; 79 | return { 80 | -or => [ 81 | 82 | # everybody can read 83 | {"$t.permissions" => {-like => '%r__'}}, 84 | 85 | # user is owner 86 | { 87 | "$t.user_id" => $user->{id}, 88 | 89 | # "$table.permissions" => {-like => '_r__%'} 90 | }, 91 | 92 | # a page or content, which can be read 93 | # by one of the groups to which this user belongs. 94 | { 95 | "$t.permissions" => {-like => '____r__%'}, 96 | "$t.group_id" => \[ 97 | "IN (?,(SELECT group_id from user_group WHERE user_id=?))" => 98 | ($user->{group_id}, $user->{id}) 99 | ], 100 | }, 101 | ], 102 | }; 103 | } 104 | 105 | # Returns hashref for where clause where permissions allow the user to write 106 | # records. 107 | sub writable_by ($self, $user) { 108 | my $t = $self->table; 109 | return { 110 | -or => [ 111 | 112 | # everybody can write 113 | {"$t.permissions" => {-like => '%_w_'}}, 114 | 115 | # user is owner 116 | {"$t.user_id" => $user->{id}, "$t.permissions" => {-like => '__w_%'}}, 117 | 118 | # a page, which can be written 119 | # by one of the groups to which this user belongs. 120 | { 121 | "$t.permissions" => {-like => '_____w_%'}, 122 | "$t.group_id" => \[ 123 | "IN (?,(SELECT group_id from user_group WHERE user_id=?))" => 124 | ($user->{group_id}, $user->{id}) 125 | ], 126 | }, 127 | ], 128 | }; 129 | } 130 | 131 | # Inserts relations for redirects from old to new alias. Must be 132 | # called only from save() and before $db->update($table,..) 133 | sub upsert_aliases ($m, $db, $alias_id, $new_alias) { 134 | my $alias_table = $m->table; 135 | my $SQL = <<"SQL"; 136 | INSERT OR IGNORE INTO aliases 137 | (old_alias,new_alias,alias_id,alias_table) 138 | VALUES ( 139 | (SELECT alias FROM $alias_table WHERE id=? AND alias != ?), 140 | ?,?,?) 141 | SQL 142 | return $db->query($SQL, $alias_id, $new_alias, $new_alias, $alias_id, $alias_table); 143 | } 144 | 145 | # Remove aliases history for a record from a given table. 146 | sub remove_aliases ($m, $db, $id, $table) { 147 | return $db->delete('aliases', {alias_table => $table, alias_id => $id}); 148 | } 149 | 150 | 1; 151 | 152 | 153 | -------------------------------------------------------------------------------- /lib/Slovo/Model/Celini.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Model::Celini; 2 | use Mojo::Base 'Slovo::Model', -signatures; 3 | 4 | my $table = 'celini'; 5 | has stranici => sub { $_[0]->c->stranici }; 6 | has main_boxes => sub { 7 | [$_[0]->c->stash->{boxes}[0]]; # main 8 | }; 9 | 10 | # Structure for matching a language parameter. 11 | sub language_like ($m, $l) { 12 | my ($l1, $l2) = $l =~ /^([A-z]{2})\-?([A-z]{2})?/; 13 | $l2 ||= $l1; 14 | return [{'=' => $l}, {-like => "$l1%"}, {-like => "%$l2"}]; 15 | } 16 | 17 | sub table { return $table } 18 | 19 | sub breadcrumb ($m, $p_alias, $path, $l, $user, $preview) { 20 | state $abstr = $m->dbx->abstract; 21 | state $s_table = $m->stranici->table; 22 | state $page_id_SQL = "= (SELECT id FROM $s_table WHERE alias=?)"; 23 | my (@SQL, @BINDS); 24 | for my $cel (@$path) { 25 | my ($u_SQL, @bind) = $abstr->select( 26 | $table, undef, 27 | { 28 | "page_id" => \[$page_id_SQL, $p_alias], 29 | "alias" => $cel, 30 | "language" => $m->celini->language_like($l), 31 | %{$m->where_with_permissions($user, $preview)}, 32 | 33 | }); 34 | push @SQL, $u_SQL; 35 | push @BINDS, @bind; 36 | } 37 | my $sql = join("\nUNION\n", @SQL); 38 | return $m->dbx->db->query($sql, @BINDS)->hashes; 39 | } 40 | 41 | sub where_with_permissions ($self, $user, $preview) { 42 | my $now = time; 43 | 44 | return { 45 | $preview ? () : ("$table.deleted" => 0), 46 | $preview ? () : ("$table.start" => [{'=' => 0}, {'<' => $now}]), 47 | $preview ? () : ("$table.stop" => [{'=' => 0}, {'>' => $now}]), 48 | -or => [ 49 | 50 | # published and everybody can read and execute 51 | $preview 52 | ? () 53 | : {"$table.published" => 2, "$table.permissions" => {-like => '%r_x'}}, 54 | 55 | # preview of a page with elements, owned by this user 56 | {"$table.user_id" => $user->{id}, "$table.permissions" => {-like => '_r_x%'}}, 57 | 58 | # preview of elements, which can be read and executed 59 | # by one of the groups to which this user belongs. 60 | { 61 | "$table.permissions" => {-like => '____r_x%'}, 62 | $preview ? () : ("$table.published" => 2), 63 | "$table.group_id" => 64 | \["IN (SELECT group_id from user_group WHERE user_id=?)" => $user->{id}], 65 | }, 66 | ]}; 67 | } 68 | 69 | sub all_for_display_in_stranica ($self, $page, $user, $l, $preview, $opts = {}) { 70 | return $self->all({ 71 | where => { 72 | page_id => $page->{id}, 73 | "$table.pid" => 0, #only content belonging directly to a page 74 | language => $self->language_like($l), 75 | %{$self->where_with_permissions($user, $preview)}, %{delete $opts->{where} // {}} 76 | }, 77 | order_by => [{-desc => [qw(featured id)]}, {-asc => [qw(sorting)]},], 78 | %$opts, 79 | }); 80 | } 81 | 82 | sub find_for_display ($m, $alias, $user, $l, $preview, $where = {}) { 83 | 84 | # 0. WHERE alias = $alias failed to match 85 | # 1. Suppose the user stumbled on a link with the old most recent alias. 86 | # 2. Look for the most recent id which had such $alias in the given table. 87 | state $old_alias_SQL = <<"SQL"; 88 | = (SELECT new_alias FROM aliases 89 | WHERE alias_table='$table' 90 | AND (old_alias=?) 91 | ORDER BY ID DESC LIMIT 1) 92 | OR $table.id=(SELECT alias_id FROM aliases 93 | WHERE alias_table='$table' 94 | AND (old_alias=? OR new_alias=?) 95 | ORDER BY ID DESC LIMIT 1) 96 | SQL 97 | 98 | 99 | #local $m->dbx->db->dbh->{TraceLevel} = "3|SQL"; 100 | my $_where = { 101 | alias => [$alias, \[$old_alias_SQL => $alias, $alias, $alias]], 102 | language => $m->language_like($l), 103 | box => $m->main_boxes, 104 | %{$m->where_with_permissions($user, $preview)}, %$where 105 | }; 106 | return $m->dbx->db->select($table, undef, $_where)->hash; 107 | } 108 | 109 | sub save ($m, $id, $row) { 110 | state $stranici_table = $m->stranici->table; 111 | 112 | # local $m->dbx->db->dbh->{TraceLevel} = "3|SQL"; 113 | $row->{tstamp} = time - 1; 114 | my $db = $m->dbx->db; 115 | eval { 116 | my $tx = $db->begin; 117 | $m->upsert_aliases($db, $id, $row->{alias}); 118 | 119 | # parent page - update its timestamp 120 | $db->update($stranici_table, {tstamp => $row->{tstamp}}, {id => $row->{page_id}}); 121 | 122 | # parent - update its timestamp 123 | $db->update($table, $row, {id => $id}); 124 | 125 | $tx->commit; 126 | } || Carp::croak("Error updating $table: $@"); 127 | return $id; 128 | } 129 | 130 | 1; 131 | -------------------------------------------------------------------------------- /lib/Slovo/Model/Domove.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Model::Domove; 2 | use Mojo::Base 'Slovo::Model', -signatures; 3 | 4 | use Mojo::Collection; 5 | 6 | my $table = 'domove'; 7 | has table => $table; 8 | has 'dbx'; 9 | 10 | sub find_by_host ($m, $h) { 11 | 12 | # Do not ask the database for the same thing on each request. Save some 13 | # method calls. We do not change domain names every day. 14 | state $cache = {}; 15 | 16 | # If needed later, we may add more columns to this query. 17 | state $sql = <<"SQL"; 18 | SELECT * FROM domove 19 | WHERE (? LIKE '%' || domain OR aliases LIKE ? OR ips LIKE ?) 20 | AND published = ? LIMIT 1 21 | SQL 22 | return $cache->{$h} //= $m->dbx->db->query($sql, $h, "%$h%", "%$h%", 2)->hash; 23 | } 24 | 25 | # Returns all published domains and caches them. We expect not more than 100 26 | # domains per Slovo instance to be served. 27 | sub all ($d) { 28 | state $all = Mojo::Collection->new(); 29 | return $all->size ? $all : $all 30 | = $d->SUPER::all({where => {published => {'>' => 1}}, order_by => {-asc => ['id']}}); 31 | } 32 | 33 | 1; 34 | -------------------------------------------------------------------------------- /lib/Slovo/Model/Groups.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Model::Groups; 2 | use Mojo::Base 'Slovo::Model', -signatures; 3 | 4 | my $table = 'groups'; 5 | 6 | sub table { return $table } 7 | 8 | my $loadable = sub { 9 | return (disabled => 0, id => {'>' => 0}); 10 | }; 11 | 12 | sub all ($self, $opts = {}) { 13 | $opts->{where} = {$loadable->(), %{$opts->{where} // {}}}; 14 | $opts->{order_by} //= {-asc => ['id']}; 15 | return $self->SUPER::all($opts); 16 | } 17 | 18 | # all groups with virtual column `is_member`. 19 | # The 'admin' and 'guest' groups are not in the list. 20 | # New admins are added only via the command line. 21 | sub all_with_member ($m, $uid) { 22 | my $columns = <<"COLS"; 23 | name, id, description AS title, disabled, 24 | coalesce((SELECT 1 from user_group 25 | WHERE user_id=$uid AND group_id=$table.id),0) 26 | AS is_member 27 | COLS 28 | 29 | return $m->all({ 30 | columns => $columns, 31 | where => {id => {-not_in => [1, 2]},}, 32 | order_by => {-desc => ['is_member', 'id']}}); 33 | } 34 | 35 | sub find ($self, $id) { 36 | return $self->dbx->db->select($table, undef, {$loadable->(), id => $id})->hash; 37 | } 38 | 39 | 40 | sub remove ($self, $id) { 41 | return $self->dbx->db->delete($table, {$loadable->(), id => $id}); 42 | } 43 | 44 | sub save ($self, $id, $row) { 45 | return $self->dbx->db->update($table, $row, {$loadable->(), id => $id}); 46 | } 47 | 48 | # Returns cached result of a check if the user is in the admin group. 49 | sub is_admin ($m, $uid) { 50 | state $admins = {}; 51 | state $Q = <<"SQL"; 52 | SELECT group_id FROM user_group 53 | WHERE user_id=? AND group_id=1 54 | LIMIT 1 55 | SQL 56 | 57 | return $admins->{$uid} //= $m->dbx->db->query($Q, $uid)->hash; 58 | } 59 | 60 | 1; 61 | -------------------------------------------------------------------------------- /lib/Slovo/Model/Users.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Model::Users; 2 | use Mojo::Base 'Slovo::Model', -signatures; 3 | 4 | my $table = 'users'; 5 | my $ug_table = 'user_group'; 6 | sub table { return $table } 7 | 8 | sub groups_table { return Slovo::Model::Groups->table } 9 | 10 | # Create a user and the primary group for the user. 11 | # Add the primary group to $ug_table. 12 | sub add ($self, $row) { 13 | my $db = $self->dbx->db; 14 | my $id; 15 | my $group_row = { 16 | name => $row->{login_name}, 17 | description => 'Главно множество за ' . $row->{login_name}, 18 | created_by => $row->{created_by}, 19 | changed_by => $row->{changed_by}, 20 | disabled => $row->{disabled}, 21 | }; 22 | eval { 23 | my $tx = $db->begin; 24 | my $gid = $db->insert(groups_table, $group_row)->last_insert_id; 25 | $row->{group_id} = $gid; 26 | $id = $db->insert($table, $row)->last_insert_id; 27 | $db->insert($ug_table => {user_id => $id, group_id => $gid}); 28 | $tx->commit; 29 | } || Carp::croak("Error creating user: $@"); 30 | return $id; 31 | } 32 | 33 | my $loadable = sub { 34 | my $time = time; 35 | return ( 36 | disabled => 0, 37 | group_id => {'>' => 0}, 38 | start_date => {'<' => $time}, 39 | stop_date => [{'=' => 0}, {'>' => $time}], 40 | ); 41 | }; 42 | 43 | sub all ($self, $opts = {}) { 44 | $opts->{limit} //= 100; 45 | $opts->{limit} = 100 unless $opts->{limit} =~ /^\d+$/; 46 | $opts->{offset} //= 0; 47 | $opts->{offset} = 0 unless $opts->{offset} =~ /^\d+$/; 48 | $opts->{columns} //= '*'; 49 | my $where = {$loadable->(), %{$opts->{where} // {}}}; 50 | state $abstr = $self->dbx->abstract; 51 | my ($sql, @bind) = $abstr->select($table, $opts->{columns}, $where); 52 | $sql .= " LIMIT $opts->{limit}" 53 | . (defined $opts->{offset} ? " OFFSET $opts->{offset}" : ''); 54 | return $self->dbx->db->query($sql, @bind)->hashes; 55 | } 56 | 57 | 58 | sub find ($self, $id) { 59 | return $self->dbx->db->select($table, undef, {id => $id, $loadable->()})->hash; 60 | } 61 | 62 | sub find_by_login_name ($self, $login_name) { 63 | return $self->dbx->db->select($table, undef, {login_name => $login_name, $loadable->()}) 64 | ->hash; 65 | } 66 | 67 | sub purge ($self, $id) { 68 | return $self->dbx->db->delete($table, {$loadable->(), id => $id}); 69 | } 70 | 71 | sub remove ($self, $id) { 72 | return $self->dbx->db->update($table, {disabled => 1}, {id => $id}); 73 | } 74 | 75 | #update a user 76 | sub save ($m, $id, $row) { 77 | 78 | #never change the primary group 79 | delete $row->{group_id}; 80 | my $groups; 81 | if ($row->{groups}) { 82 | $groups 83 | = ref $row->{groups} eq 'ARRAY' ? delete $row->{groups} : [delete $row->{groups}]; 84 | } 85 | my $db = $m->dbx->db; 86 | 87 | state $gid_SQL = "(SELECT group_id FROM $table WHERE id=?)"; 88 | eval { 89 | my $tx = $db->begin; 90 | $db->update($table, $row, {id => $id}); 91 | 92 | # Remove all previous groups except primary and insert the selected groups. 93 | if ($groups) { 94 | $db->delete( 95 | $ug_table => {user_id => $id, group_id => {'!=' => \[$gid_SQL => $id]}}); 96 | for my $gid (@$groups) { 97 | $db->query("INSERT OR IGNORE INTO $ug_table VALUES (?,?)", $id, $gid); 98 | } 99 | } 100 | 101 | # disable/enable primary group if needed 102 | $db->update( 103 | groups_table, 104 | {disabled => $row->{disabled}}, 105 | {id => {'=' => \[$gid_SQL => $id]}}) if defined $row->{disabled}; 106 | $tx->commit; 107 | } || Carp::croak("Error updating $table: $@"); 108 | return $id; 109 | } 110 | 111 | 1; 112 | -------------------------------------------------------------------------------- /lib/Slovo/Plugin/CGI.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Plugin::CGI; 2 | use Mojo::Base 'Mojolicious::Plugin', -signatures; 3 | 4 | use Mojo::Util qw(punycode_decode); 5 | 6 | my $mod_rewrite; 7 | 8 | sub register ($self, $app, $config) { 9 | 10 | # Check again if we need this hook at all 11 | return $self unless $ENV{GATEWAY_INTERFACE}; 12 | $mod_rewrite = $config->{mod_rewrite} //= 1; 13 | $app->hook(before_dispatch => \&_handle_cgi); 14 | return $self; 15 | } 16 | 17 | sub _handle_cgi ($c) { 18 | my $url = $c->req->url; 19 | my $path = $url->path->to_string || '/'; 20 | $path =~ s|^.+cached/?||; 21 | if ($path =~ m'%') { 22 | $path = Mojo::Util::url_unescape $path; 23 | $path = Mojo::Util::decode 'UTF-8', $path; 24 | } 25 | 26 | # no path merging 27 | $url->path($path =~ m'^/' ? $path : "/$path"); 28 | 29 | # remove /slovo/slovo.cgi from generated links in the page 30 | if ($mod_rewrite) { 31 | my $base = $url->base =~ s|$ENV{SCRIPT_NAME}||r; 32 | $url->base(Mojo::URL->new($base)); 33 | } 34 | 35 | return; 36 | } 37 | 38 | 1; 39 | 40 | =encoding utf8 41 | 42 | =head1 NAME 43 | 44 | Slovo::Plugin::CGI - before_dispatch hook under Apache 2/CGI 45 | 46 | 47 | =head1 DESCRIPTION 48 | 49 | L extends L. It provides a 50 | L hook to handle Apache double encoding of the current url 51 | and to strip the C<$ENV{SCRIPT_NAME}> from produced by Slovo urls. This plugin 52 | is enabled by default and will detect if the app is run under CGI. If app is 53 | run as a daemon this plugin will do nothing. 54 | 55 | =head1 HOOKS 56 | 57 | 58 | =head2 before_dispatch 59 | 60 | Handles Apache double encoding of the UTf-8 url and strips 61 | the C<$ENV{SCRIPT_NAME}> from produced by Slovo urls. 62 | 63 | =head1 CONFIGURATION 64 | 65 | The folowing option is currently supported. 66 | 67 | =head2 mod_rewrite = 1 68 | 69 | Boolean. Defaluts to true. Set to 0 to disable removing of C<$ENV{SCRIPT_NAME}> 70 | from C<$c-Ereq-Eurl-Ebase>. This will practically stop removing 71 | C<$ENV{SCRIPT_NAME}> from all generated links. Having C<$ENV{SCRIPT_NAME}> in 72 | URLs also tells mod rewrite to not look for static files in folder domove to 73 | not apply any rules when requestingC<$ENV{SCRIPT_NAME}> 74 | 75 | =head1 AUTHOR 76 | 77 | Красимир Беров 78 | CPAN ID: BEROV 79 | berov на cpan точка org 80 | http://i-can.eu 81 | 82 | =head1 COPYRIGHT 83 | 84 | This program is free software licensed under the Artistic License 2.0. 85 | 86 | The full text of the license can be found in the 87 | LICENSE file included with this module. 88 | 89 | =cut 90 | 91 | -------------------------------------------------------------------------------- /lib/Slovo/Validator.pm: -------------------------------------------------------------------------------- 1 | package Slovo::Validator; 2 | use Mojo::Base 'Mojolicious::Validator', -signatures; 3 | 4 | # can this $name with $value do $sub with @args? 5 | my sub _can ($v, $name, $value, $sub, @args) { 6 | return !$sub->($v, $name, $value, @args); 7 | } 8 | 9 | sub new { 10 | my $self = shift->SUPER::new(@_); 11 | 12 | # new filters 13 | $self->add_filter(xml_escape => sub { Mojo::Util::xml_escape($_[2]) }); 14 | $self->add_filter(slugify => sub { Mojo::Util::slugify($_[2], 1) }); 15 | 16 | # new checks 17 | $self->add_check(is => \&_can); 18 | $self->add_check( 19 | equals => sub ($v, $name, $value, $eq) { 20 | unless ($value eq $eq) { 21 | return 1; 22 | } 23 | return; 24 | }); 25 | return $self; 26 | } 27 | 28 | 1; 29 | 30 | =encoding utf8 31 | 32 | =head1 NAME 33 | 34 | Slovo::Validator - additional validator filters and checks 35 | 36 | =head1 CHECKS 37 | 38 | Slovo::Validator inherits all checks from Mojolicious::Validator and implements 39 | the following new ones. 40 | 41 | =head2 is 42 | 43 | A custom check -- some code reference which returns true on success, false 44 | otherwise. 45 | 46 | # in the action 47 | $v->required('id')->is(\&_writable_by, $c->stranici, $c->user); 48 | 49 | # in the same or parent controller 50 | sub _writable_by ($v, $id_name, $id_value, $m, $user) { 51 | return !!$m->find_where({$id_name => $id_value, %{$m->writable_by($user)}}); 52 | } 53 | 54 | # or simply 55 | $v->required('sum')->is(sub($v, $name, $value) { 56 | $v->param('one') + $v->param('two') == $value 57 | }); 58 | 59 | =head1 FILTERS 60 | 61 | Slovo::Validator inherits all filters from Mojolicious::Validator and 62 | implements the following new ones. 63 | 64 | =head2 slugify 65 | 66 | $v->required('alias', 'slugify')->size(0, 255); 67 | 68 | Generate URL slug for bytestream with L. 69 | 70 | =head2 xml_escape 71 | 72 | $c->validation->optional(title => xml_escape => 'trim')->size(10, 255); 73 | 74 | Uses L to escape unsafe characters. Returns the escaped 75 | string. 76 | 77 | =head1 SEE ALSO 78 | 79 | L, L 80 | 81 | =cut 82 | 83 | -------------------------------------------------------------------------------- /lib/Slovo/resources/data/used-unicode-symbols.txt: -------------------------------------------------------------------------------- 1 | --- 2 | U+2B88 ⮈ e2 ae 88 LEFT­WARDS BLACK CIRCLED WHITE ARROW 3 | U+2B89 ⮉ e2 ae 89 UPWARDS BLACK CIRCLED WHITE ARROW 4 | U+2B8A ⮊ e2 ae 8a RIGHT­WARDS BLACK CIRCLED WHITE ARROW 5 | U+2B8B ⮋ e2 ae 8b DOWNWARDS BLACK CIRCLED WHITE ARROW 6 | U+2B00 ⬀ e2 ac 80 NORTH EAST WHITE ARROW 7 | U+2B01 ⬁ e2 ac 81 NORTH WEST WHITE ARROW 8 | U+2B02 ⬂ e2 ac 82 SOUTH EAST WHITE ARROW 9 | U+2B03 ⬃ e2 ac 83 SOUTH WEST WHITE ARROW 10 | U+2B04 ⬄ e2 ac 84 LEFT RIGHT WHITE ARROW 11 | U+2B05 ⬅ e2 ac 85 LEFT­WARDS BLACK ARROW 12 | U+2B06 ⬆ e2 ac 86 UPWARDS BLACK ARROW 13 | U+2B07 ⬇ e2 ac 87 DOWNWARDS BLACK ARROW 14 | U+2B08 ⬈ e2 ac 88 NORTH EAST BLACK ARROW 15 | U+2B09 ⬉ e2 ac 89 NORTH WEST BLACK ARROW 16 | U+2B0A ⬊ e2 ac 8a SOUTH EAST BLACK ARROW 17 | U+2B0B ⬋ e2 ac 8b SOUTH WEST BLACK ARROW 18 | U+2B0C ⬌ e2 ac 8c LEFT RIGHT BLACK ARROW 19 | U+2B0D ⬍ e2 ac 8d UP DOWN BLACK ARROW 20 | U+2397 ⎗ e2 8e 97 PREVIOUS PAGE 21 | U+2398 ⎘ e2 8e 98 NEXT PAGE 22 | --- 23 | U+1D34C 𝍌 f0 9d 8d 8c TETRAGRAM FOR STOPPAGE 24 | U+1F4C3 📃 f0 9f 93 83 PAGE WITH CURL 25 | U+1F4C4 📄 f0 9f 93 84 PAGE FACING UP 26 | U+1F4DF 📟 f0 9f 93 9f PAGER 27 | U+1F57C 🕼 f0 9f 95 bc TELEPHONE RECEIVER WITH PAGE 28 | U+1F5C6 🗆 f0 9f 97 86 EMPTY NOTE PAGE 29 | U+1F5C9 🗉 f0 9f 97 89 NOTE PAGE 30 | U+1F5CC 🗌 f0 9f 97 8c EMPTY PAGE 31 | U+1F5CD 🗍 f0 9f 97 8d EMPTY PAGES 32 | U+1F5CF 🗏 f0 9f 97 8f PAGE 33 | U+1F5D0 🗐 f0 9f 97 90 PAGES 34 | U+1F5DF 🗟 f0 9f 97 9f PAGE WITH CIRCLED TEXT 35 | --- 36 | U+270E ✎ e2 9c 8e LOWER RIGHT PENCIL 37 | U+270F ✏ e2 9c 8f PENCIL 38 | U+2710 ✐ e2 9c 90 UPPER RIGHT PENCIL 39 | --- 40 | U+1F3F1 🏱 f0 9f 8f b1 WHITE PENNANT 41 | U+1F3F2 🏲 f0 9f 8f b2 BLACK PENNANT 42 | U+1F427 🐧 f0 9f 90 a7 PENGUIN 43 | U+1F450 👐 f0 9f 91 90 OPEN HANDS SIGN 44 | U+1F4C2 📂 f0 9f 93 82 OPEN FILE FOLDER 45 | U+1F4D6 📖 f0 9f 93 96 OPEN BOOK 46 | U+1F4EC 📬 f0 9f 93 ac OPEN MAILBOX WITH RAISED FLAG 47 | U+1F4ED 📭 f0 9f 93 ad OPEN MAILBOX WITH LOWERED FLAG 48 | U+1F4C1 📁 f0 9f 93 81 FILE FOLDER 49 | U+1F4C2 📂 f0 9f 93 82 OPEN FILE FOLDER 50 | U+1F5BF 🖿 f0 9f 96 bf BLACK FOLDER 51 | U+1F5C0 🗀 f0 9f 97 80 FOLDER 52 | U+1F5C1 🗁 f0 9f 97 81 OPEN FOLDER 53 | --- 54 | Uni­code code point U+1F4DD 55 | char­acter 📝 56 | UTF-8 en­co­ding f0 9f 93 9d hexa­decimal 57 | 240 159 147 157 deci­mal 58 | 0360 0237 0223 0235 octal 59 | HTML en­co­ding 📝 📝 hexa­decimal 60 | 📝 📝 deci­mal 61 | Uni­co­de char­ac­ter name MEMO 62 | --- 63 | U+1F4BE 💾 f0 9f 92 be FLOPPY DISK 64 | U+1F5AA 🖪 f0 9f 96 aa BLACK HARD SHELL FLOPPY DISK 65 | U+1F5AB 🖫 f0 9f 96 ab WHITE HARD SHELL FLOPPY DISK 66 | U+1F5AC 🖬 f0 9f 96 ac SOFT SHELL FLOPPY DISK 67 | U+1F5B9 🖹 f0 9f 96 b9 DOCUMENT WITH TEXT 68 | U+1F5BA 🖺 f0 9f 96 ba DOCUMENT WITH TEXT AND PICTURE 69 | U+1F5BB 🖻 f0 9f 96 bb DOCUMENT WITH PICTURE 70 | U+1F5CB 🗋 f0 9f 97 8b EMPTY DOCUMENT 71 | U+1F5CE 🗎 f0 9f 97 8e DOCUMENT 72 | U+1F377 🍷 f0 9f 8d b7 WINE GLASS 73 | U+1F378 🍸 f0 9f 8d b8 COCKTAIL GLASS 74 | U+1F50D 🔍 f0 9f 94 8d LEFT-POINTING MAGNIFYING GLASS 75 | U+1F50E 🔎 f0 9f 94 8e RIGHT-POINTING MAGNIFYING GLASS 76 | U+1F943 🥃 f0 9f a5 83 TUMBLER GLASS 77 | U+1F95B 🥛 f0 9f a5 9b GLASS OF MILK 78 | U+2611 ☑ e2 98 91 BALLOT BOX WITH CHECK 79 | U+2705 ✅ e2 9c 85 WHITE HEAVY CHECK MARK 80 | U+2713 ✓ e2 9c 93 CHECK MARK 81 | U+2714 ✔ e2 9c 94 HEAVY CHECK MARK 82 | U+2630 ☰ e2 98 b0 TRIGRAM FOR HEAVEN 83 | U+2631 ☱ e2 98 b1 TRIGRAM FOR LAKE 84 | U+2632 ☲ e2 98 b2 TRIGRAM FOR FIRE 85 | U+2633 ☳ e2 98 b3 TRIGRAM FOR THUNDER 86 | U+2634 ☴ e2 98 b4 TRIGRAM FOR WIND 87 | U+2635 ☵ e2 98 b5 TRIGRAM FOR WATER 88 | U+2636 ☶ e2 98 b6 TRIGRAM FOR MOUNTAIN 89 | U+2637 ☷ e2 98 b7 TRIGRAM FOR EARTH 90 | U+270C ✌ e2 9c 8c VICTORY HAND 91 | U+270D ✍ e2 9c 8d WRITING HAND 92 | U+1F58E 🖎 f0 9f 96 8e LEFT WRITING HAND 93 | -------------------------------------------------------------------------------- /lib/Slovo/resources/etc/README: -------------------------------------------------------------------------------- 1 | In this folder we keep configuration files. 2 | 3 | You may want to create environment specific versions of slovo.conf named after 4 | the environment e.g. slovo.development.conf or slovo.production.conf. The 5 | environment specific files will be detected and used. These files may be 6 | changed locally for experimenting but local changes usualy do not have to go 7 | upstream. To ignore such files but keep them versioned one have to run the 8 | command "git update-index". Here is an example with slovo.conf. 9 | 10 | git update-index --assume-unchanged etc/slovo.development.conf 11 | 12 | To make git track the file again, when you have a change that you want to push 13 | upstream, simply run: 14 | 15 | git update-index --no-assume-unchanged etc/slovo.development.conf. 16 | 17 | See also: 18 | https://help.github.com/articles/ignoring-files#ignoring-versioned-files 19 | 20 | -------------------------------------------------------------------------------- /lib/Slovo/resources/etc/slovo.conf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use Mojo::Base -strict, -signatures; 3 | use Mojo::Util qw(sha1_sum encode); 4 | my $home = $app->home; 5 | my $mode = $app->mode; 6 | my $moniker = $app->moniker; 7 | my $resources = $app->resources; 8 | my $rmf = $resources->child("etc/routes.$mode.conf"); 9 | 10 | 11 | my $mail_cfg = { 12 | token_valid_for => 24 * 3600, 13 | 'Net::SMTP' => { 14 | new => { 15 | Host => 'mail.example.org', 16 | 17 | #Debug => 1, 18 | SSL => 1, 19 | SSL_version => 'TLSv1', 20 | SSL_verify_mode => 0, 21 | Timeout => 60, 22 | }, 23 | auth => ['slovo@example.org', 'Pa55w03D'], 24 | mail => 'slovo@example.org', 25 | }, 26 | }; 27 | 28 | { 29 | # Hypnotoad Settings (optimized for mostly blocking operations) 30 | # See /perldoc/Mojo/Server/Hypnotoad#SETTINGS 31 | # and /perldoc/Mojolicious/Guides/Cookbook#Hypnotoad 32 | hypnotoad => { 33 | accepts => 200, 34 | graceful_timeout => 15, 35 | listen => ['http://127.0.0.1:9090', 'http://[::1]:9090'], 36 | proxy => 1, 37 | pid_file => -d $home->child('bin') 38 | ? $home->child('bin', $moniker . '.pid') 39 | : $home->child('script', $moniker . '.pid'), 40 | spare => 4, 41 | workers => 10, 42 | clients => 2, 43 | }, 44 | 45 | # Some classes which plugins or the application expect to be loaded to enable 46 | # some functionality or to refer to their functions. 47 | load_classes => [qw()], 48 | 49 | # Plugins can be Mojolicious and Slovo plugins. Every Slovo::Plugin:: ISA 50 | # Mojolicious::Plugin. Plugin order is important. Any plugin depending on 51 | # another must come after the plugin it depends on. A plugin may be loaded 52 | # twice if it will do different things depending on configuration variables. 53 | load_plugins => [ 54 | $mode =~ /^dev/ ? {PODViewer => {default_module => 'Slovo'}} : (), 55 | 56 | # In CGI mode startup and runtime are the same thing, so we check here if we 57 | # need this plugin and load it to handle the request. 58 | $ENV{GATEWAY_INTERFACE} ? 'CGI' : (), 59 | {MojoDBx => {auto_migrate => 0}}, 60 | { 61 | Authentication => { 62 | autoload_user => 1, 63 | session_key => 'u', 64 | current_user_fn => Slovo::Controller::Auth::current_user_fn(), 65 | load_user => \&Slovo::Controller::Auth::load_user, 66 | validate_user => \&Slovo::Controller::Auth::validate_user, 67 | } 68 | }, 69 | qw(DefaultHelpers TagHelpers), 70 | {RoutesConfig => {file => (-e $rmf ? $rmf : $resources->child("etc/routes.conf"))}}, 71 | { 72 | 'Minion::Admin' => 73 | sub { { 74 | route => $app->routes->lookup('home_minion'), return_to => 'home_upravlenie', 75 | } } 76 | }, 77 | { 78 | Minion => sub { {SQLite => $app->dbx} } 79 | }, 80 | 81 | {OpenAPI => {url => $resources->child("api-v1.0.json")->to_string}}, 82 | 83 | # Tasks 84 | {'Task::SendOnboardingEmail' => $mail_cfg}, 85 | {'Task::SendPasswEmail' => $mail_cfg}, 86 | 87 | # Themes. The precedence is depending on the order here. 88 | "Themes::Malka" 89 | ], 90 | 91 | secrets => [sha1_sum(encode('utf8', $home . 'тайна')),], 92 | 93 | # See also /perldoc/Mojolicious/Sessions 94 | sessions => [ 95 | 96 | #attribute => value 97 | {default_expiration => 3600 * 24 * 5}, #five days 98 | {cookie_name => 'slovo'}, 99 | ], 100 | 101 | # Root folder where domain specific files will reside. Each domain has it's 102 | # own folder there. 103 | domove_root => $home->child('domove')->to_string, 104 | 105 | # Cache published pages for non authenticated users 106 | $mode =~ /^dev/ ? () : (cache_pages => 1), 107 | 108 | # Cache-Control header for non authenticated users. For authenticated users 109 | # the max-age is the same, but 'public' is replaced with 'private'. 110 | cache_control => 'public, max-age=' . (1 * 3600), # 1 hour 111 | response_compression => 0, 112 | } 113 | 114 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/css/fonts.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /* Veleka2 (Serif) */ 4 | @font-face { 5 | font-family: 'Veleka'; 6 | src: url('/fonts/veleka2-regular-01.woff2') format('woff2'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'FreeSerif'; 13 | src: url('/fonts/freeserif-webfont.woff2') format('woff2'); 14 | font-weight: normal; 15 | font-style: normal; 16 | } 17 | 18 | /* Sans */ 19 | @font-face { 20 | font-family: 'FreeSans'; 21 | src: url('/fonts/freesans-webfont.woff2') format('woff2'); 22 | font-weight: normal; 23 | font-style: normal; 24 | } 25 | 26 | /* Mono */ 27 | @font-face { 28 | font-family: 'FreeMono'; 29 | src: url('/fonts/freemono-webfont.woff2') format('woff2'); 30 | font-weight: normal; 31 | font-style: normal; 32 | } 33 | 34 | @font-face { 35 | font-family: 'BukyvedeRegular'; 36 | src: url('/fonts/bukyvede-regular-webfont.woff2') format('woff2'); 37 | font-weight: normal; 38 | font-style: normal; 39 | } 40 | 41 | /* Generic font styles */ 42 | body { 43 | font-family: BukyvedeRegular, FreeSans, sans-serif; 44 | } 45 | 46 | header.mui-appbar .mui--text-title, footer.mui-appbar, #sidedrawer { 47 | font-family: BukyvedeRegular; 48 | font-weight: bolder; 49 | } 50 | 51 | main, main section, article { 52 | font-family: Veleka, FreeSerif, BukyvedeRegular, serif; 53 | } 54 | 55 | .блъгарьскъ { 56 | font-family: BukyvedeRegular; 57 | font-weight: bold; 58 | } 59 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/css/upravlenie.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | html, 4 | body { 5 | height: 100%; 6 | background-color: #eee; 7 | font-size:16px; 8 | /* TODO: change body/html font-size dynamically to change the lenght values 9 | * to everything set in "rem"s (root ems). Useful for screens with bigger 10 | * resolution where the font is too small. 11 | */ 12 | } 13 | 14 | html, 15 | body, 16 | input, 17 | textarea, 18 | button { 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004); 22 | } 23 | 24 | .mui-textfield > .field-with-error, 25 | .field-with-error { 26 | color: #fff; 27 | background-color: #F44336; 28 | font-size:1.5em; 29 | } 30 | 31 | .hidden, .deleted, .disabled, .expired, .upcoming {opacity: 0.50;} 32 | .deleted, .disabled {text-decoration: line-through red;} 33 | .upcoming {text-decoration: green wavy overline;} 34 | .expired {text-decoration: green wavy underline;} 35 | #logo { 36 | height: 1rem; 37 | vertical-align:middle; 38 | } 39 | 40 | .item-field { 41 | border: 1px #ddd solid; 42 | margin: 1ex 0 1ex 1ex; 43 | display: block; 44 | } 45 | 46 | td.buttons { 47 | font-size: 1.5rem; 48 | } 49 | 50 | input, textarea, option, button { 51 | font-size: 1rem; 52 | } 53 | 54 | .mui--appbar-min-height, .mui-appbar, .mui--appbar-height { 55 | min-height: 1rem; 56 | max-height: 6rem; 57 | cursor: default; 58 | } 59 | 60 | footer.mui-appbar table, header.mui-appbar table { 61 | height: 100%; 62 | width: 100%; 63 | padding: 0 1rem 0 1rem; 64 | } 65 | 66 | header a, footer a { 67 | color: white; 68 | } 69 | 70 | .b { 71 | font-weight:bold; 72 | } 73 | 74 | .mui-select > label, 75 | .mui-textfield > label { 76 | line-height: 1.1em; 77 | font-size: 1.2rem; 78 | } 79 | 80 | .trumbowyg-editor { 81 | font-family: Veleka, FreeSerif, BukyvedeRegular, serif; 82 | } 83 | 84 | .mui-panel { 85 | padding: .3rem; 86 | } 87 | 88 | .mui-panel.breadcrumb a:first-child { 89 | float:right; 90 | } 91 | /* Pages: Font Awesome 5 Free styles */ 92 | .pages .mui-dropdown { 93 | /* float:right; */ 94 | } 95 | 96 | .pages .mui-dropdown__menu li a { 97 | font-family: "Font Awesome 5 Free"; 98 | } 99 | .pages .mui-btn--small { 100 | width: 1.2rem; 101 | height: 1.2rem; 102 | line-height: 1.2rem; 103 | padding: .1rem; 104 | } 105 | 106 | .mui-btn--small .mui-caret {margin:0;} 107 | 108 | 109 | .pages ul .fa-li { 110 | left: -1em; 111 | width: 100%; 112 | /* border-top: 1px dotted; */ 113 | text-align: left; 114 | } 115 | 116 | .mui-form>legend {font-size: 2rem;} 117 | .mui-form>fieldset>legend { 118 | font-size: 1.5rem; 119 | cursor: pointer; 120 | } 121 | 122 | .mui-form>fieldset>legend {color: rgba(0,0,0,.54);} 123 | .mui-form>fieldset>legend:hover {color: rgba(0,0,0,1);} 124 | 125 | .mui-select__menu { 126 | z-index:100; 127 | } 128 | .mui-select__menu>.mui--is-selected { 129 | font-weight: bold; 130 | } 131 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/fonts/README: -------------------------------------------------------------------------------- 1 | This folder contains some free fonts and subsets of free fonts. 2 | Fonts are: 3 | 4 | FreeFont: https://www.gnu.org/software/freefont/ 5 | A subset from it only is used for the purpose of the demo site. The subset was 6 | generated using https://www.fontsquirrel.com/tools/webfont-generator . See 7 | generator_config.txt for the specific subset. 8 | 9 | Menaion Unicode 2.0, a font for Ustav-era Church Slavonic typography. 10 | The Menaion font was originally designed by Victor A. Baranov at http://www.manuscripts.ru/ 11 | Reencoded for Unicode by Aleksandr Andreev for the Ponomar Project (http://www.ponomar.net/). 12 | Used with permission of the original author. 13 | Menaion Unicode is Copyright (C) 2013-2016 Aleksandr Andreev. 14 | Portions Copyright (C) 2010 Victor A. Baranov. 15 | Menaion Unicode is licensed EITHER: 16 | * under the SIL Open Font License version 1.1, a copy of which is reproduced in OFL.txt 17 | * or, as part of the Ponomar software, under the GNU General Public License, v. 3 18 | (or any later version) with the font exception (see LICENSE for details) 19 | AT YOUR CHOOSING. 20 | 21 | Font Veleka | Шрифт Велека 22 | Copyright (c) by Stefan Peev, Context Ltd, 2016 (context.bg[at]gmail.com, 23 | http://www.contextbg.net), under SIL license with Reserved Font Name "Veleka". 24 | Veleka is complete modification of Charis SIL, copyrighted (c) by SIL 25 | International, 1997-2014 under SIL license (the SIL license is attached with 26 | the Veleka font, and is also available with a FAQ at: 27 | http://scripts.sil.org/OFL). Although Charis SIL is intended to be a 28 | multilingual font it has some limitations, which were the reason to create the 29 | Veleka font. 30 | 31 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/fonts/bukyvede-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/fonts/bukyvede-regular-webfont.woff2 -------------------------------------------------------------------------------- /lib/Slovo/resources/public/fonts/freemono-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/fonts/freemono-webfont.woff2 -------------------------------------------------------------------------------- /lib/Slovo/resources/public/fonts/freesans-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/fonts/freesans-webfont.woff2 -------------------------------------------------------------------------------- /lib/Slovo/resources/public/fonts/freeserif-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/fonts/freeserif-webfont.woff2 -------------------------------------------------------------------------------- /lib/Slovo/resources/public/fonts/veleka2-regular-01.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/fonts/veleka2-regular-01.woff2 -------------------------------------------------------------------------------- /lib/Slovo/resources/public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/img/favicon.ico -------------------------------------------------------------------------------- /lib/Slovo/resources/public/img/slovo-white-big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/img/slovo-white-big.png -------------------------------------------------------------------------------- /lib/Slovo/resources/public/img/slovo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/img/slovo-white.png -------------------------------------------------------------------------------- /lib/Slovo/resources/public/img/slovo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 17 | 22 | 28 | 33 | 38 | 44 | 45 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 62 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/img/slovo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/img/slovo.png -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/CryptoJS-v3.1.2/sha1.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(e,m){var p={},j=p.lib={},l=function(){},f=j.Base={extend:function(a){l.prototype=this;var c=new l;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | n=j.WordArray=f.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=m?c:4*a.length},toString:function(a){return(a||h).stringify(this)},concat:function(a){var c=this.words,q=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 9 | 32-8*(c%4);a.length=e.ceil(c/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b>>2]>>>24-8*(d%4)&255;b.push((f>>>4).toString(16));b.push((f&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d>>3]|=parseInt(a.substr(d, 10 | 2),16)<<24-4*(d%8);return new n.init(b,c/2)}},g=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d>>2]>>>24-8*(d%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d>>2]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return new n.init(b,c)}},r=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(g.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return g.parse(unescape(encodeURIComponent(a)))}}, 11 | k=j.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new n.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,d=c.sigBytes,f=this.blockSize,h=d/(4*f),h=a?e.ceil(h):e.max((h|0)-this._minBufferSize,0);a=h*f;d=e.min(4*a,d);if(a){for(var g=0;ga;a++){if(16>a)l[a]=f[n+a]|0;else{var c=l[a-3]^l[a-8]^l[a-14]^l[a-16];l[a]=c<<1|c>>>31}c=(h<<5|h>>>27)+j+l[a];c=20>a?c+((g&e|~g&k)+1518500249):40>a?c+((g^e^k)+1859775393):60>a?c+((g&e|g&k|e&k)-1894007588):c+((g^e^ 15 | k)-899497514);j=k;k=e;e=g<<30|g>>>2;g=h;h=c}b[0]=b[0]+h|0;b[1]=b[1]+g|0;b[2]=b[2]+e|0;b[3]=b[3]+k|0;b[4]=b[4]+j|0},_doFinalize:function(){var f=this._data,e=f.words,b=8*this._nDataBytes,h=8*f.sigBytes;e[h>>>5]|=128<<24-h%32;e[(h+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(h+64>>>9<<4)+15]=b;f.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=j.clone.call(this);e._hash=this._hash.clone();return e}});e.SHA1=j._createHelper(m);e.HmacSHA1=j._createHmacHelper(m)})(); 16 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editor.js: -------------------------------------------------------------------------------- 1 | //Associate an editor with the data_format of the body of a celina 2 | jQuery(function($) { 3 | 'use strict'; 4 | let Editors = { 5 | $selector: 'textarea[name="body"]', 6 | $body_id: '_body', 7 | // A simple textarea 8 | text: function() { 9 | let $body = this.$selector; 10 | //get the value to keep it 11 | let $value = $($body).val(); 12 | //empty the container 13 | $('#'+this.$body_id).empty(); 14 | // recreate the textarea 15 | $('#'+this.$body_id).append(``); 17 | $($body).val($value); 18 | }, 19 | //Apply a html editor to the textareaea 20 | html: function() { 21 | this.text(); 22 | $(this.$selector).trumbowyg({ 23 | btns: [ 24 | ['viewHTML'], 25 | ['undo', 'redo'], // Only supported in Blink browsers 26 | ['formatting'], 27 | ['strong', 'em', 'del'], 28 | ['superscript', 'subscript'], 29 | ['link'], 30 | ['insertImage', 'base64'], 31 | ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], 32 | ['unorderedList', 'orderedList'], 33 | ['horizontalRule'], 34 | ['removeformat'], 35 | ['fullscreen'] 36 | ], 37 | removeformatPasted: true, 38 | lang: 'bg' 39 | }); 40 | }, 41 | markdown: function() { 42 | this.text(); 43 | let editor_body = $(`#${this.$body_id}`); 44 | let height = editor_body.css('height'); 45 | let editor = editormd(this.$body_id, { 46 | width : 'auto', 47 | watch: false, 48 | toolbarIcons: [ 49 | "undo", "redo", "|", "bold", "del", "italic", "quote", "uppercase", 50 | "lowercase", "|", "h1", "h2", "h3", "h4", "h5", "h6", "|", 51 | "list-ul", "list-ol", "hr", "|", "link", "reference-link", "image", 52 | "code", "code-block", "table", 53 | "watch", "preview", "fullscreen", "|", "help", "info" 54 | ], 55 | path : '/js/editormd/lib/' 56 | }); 57 | editor.editor.css({position: 'relaive', top: '1rem'}) 58 | editor.editor.height(height) 59 | } 60 | }; // End Editors 61 | 62 | let $format_s = $('[name="data_format"]'); 63 | let $v = $format_s.val(); 64 | if (Editors.hasOwnProperty($v)) Editors[$v](); 65 | else Editors.text(); 66 | 67 | function switch_editors() { 68 | let $v = this.value; 69 | if (Editors.hasOwnProperty($v)) Editors[$v](); 70 | else 71 | Editors.text(); 72 | } 73 | 74 | $format_s.on('change', switch_editors); 75 | 76 | 77 | // Additional functionality - hiding and showing fieldsets in stranici and 78 | // celini forms. 79 | $(".mui-form>fieldset>legend").click(function() { 80 | let self = this; 81 | let rows = $(".mui-row", this.parentNode); 82 | let errors = $(".mui-row .field-with-error", this.parentNode); 83 | // Hide fieldsets only if there are no validation errors 84 | if (rows.is(":visible") && errors.get(0) == null) { 85 | rows.hide(600, function() { 86 | let text = $(self).text(); 87 | if (!text.match(/…$/)) $(self).text($(self).text() + "…"); 88 | }); 89 | } else { 90 | rows.show(600, function() { 91 | let text = $(self).text(); 92 | $(self).text(text.replace(/…/, "")); 93 | }); 94 | } 95 | }); 96 | 97 | // hide Permissions and additional fields by default. 98 | $(".mui-form>fieldset:nth-of-type(2)>legend").click(); 99 | $(".mui-form>fieldset:nth-of-type(3)>legend").click(); 100 | 101 | }); 102 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.eot -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.ttf -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/editormd-logo.woff -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/lib/Slovo/resources/public/js/editormd/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/languages/zh-tw.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | var factory = function (exports) { 3 | var lang = { 4 | name : "zh-tw", 5 | description : "開源在線Markdown編輯器
Open source online Markdown editor.", 6 | tocTitle : "目錄", 7 | toolbar : { 8 | undo : "撤銷(Ctrl+Z)", 9 | redo : "重做(Ctrl+Y)", 10 | bold : "粗體", 11 | del : "刪除線", 12 | italic : "斜體", 13 | quote : "引用", 14 | ucwords : "將所選的每個單詞首字母轉成大寫", 15 | uppercase : "將所選文本轉成大寫", 16 | lowercase : "將所選文本轉成小寫", 17 | h1 : "標題1", 18 | h2 : "標題2", 19 | h3 : "標題3", 20 | h4 : "標題4", 21 | h5 : "標題5", 22 | h6 : "標題6", 23 | "list-ul" : "無序列表", 24 | "list-ol" : "有序列表", 25 | hr : "横线", 26 | link : "链接", 27 | "reference-link" : "引用鏈接", 28 | image : "圖片", 29 | code : "行內代碼", 30 | "preformatted-text" : "預格式文本 / 代碼塊(縮進風格)", 31 | "code-block" : "代碼塊(多語言風格)", 32 | table : "添加表格", 33 | datetime : "日期時間", 34 | emoji : "Emoji 表情", 35 | "html-entities" : "HTML 實體字符", 36 | pagebreak : "插入分頁符", 37 | watch : "關閉實時預覽", 38 | unwatch : "開啟實時預覽", 39 | preview : "全窗口預覽HTML(按 Shift + ESC 退出)", 40 | fullscreen : "全屏(按 ESC 退出)", 41 | clear : "清空", 42 | search : "搜尋", 43 | help : "使用幫助", 44 | info : "關於" + exports.title 45 | }, 46 | buttons : { 47 | enter : "確定", 48 | cancel : "取消", 49 | close : "關閉" 50 | }, 51 | dialog : { 52 | link : { 53 | title : "添加鏈接", 54 | url : "鏈接地址", 55 | urlTitle : "鏈接標題", 56 | urlEmpty : "錯誤:請填寫鏈接地址。" 57 | }, 58 | referenceLink : { 59 | title : "添加引用鏈接", 60 | name : "引用名稱", 61 | url : "鏈接地址", 62 | urlId : "鏈接ID", 63 | urlTitle : "鏈接標題", 64 | nameEmpty: "錯誤:引用鏈接的名稱不能為空。", 65 | idEmpty : "錯誤:請填寫引用鏈接的ID。", 66 | urlEmpty : "錯誤:請填寫引用鏈接的URL地址。" 67 | }, 68 | image : { 69 | title : "添加圖片", 70 | url : "圖片地址", 71 | link : "圖片鏈接", 72 | alt : "圖片描述", 73 | uploadButton : "本地上傳", 74 | imageURLEmpty : "錯誤:圖片地址不能為空。", 75 | uploadFileEmpty : "錯誤:上傳的圖片不能為空!", 76 | formatNotAllowed : "錯誤:只允許上傳圖片文件,允許上傳的圖片文件格式有:" 77 | }, 78 | preformattedText : { 79 | title : "添加預格式文本或代碼塊", 80 | emptyAlert : "錯誤:請填寫預格式文本或代碼的內容。" 81 | }, 82 | codeBlock : { 83 | title : "添加代碼塊", 84 | selectLabel : "代碼語言:", 85 | selectDefaultText : "請語言代碼語言", 86 | otherLanguage : "其他語言", 87 | unselectedLanguageAlert : "錯誤:請選擇代碼所屬的語言類型。", 88 | codeEmptyAlert : "錯誤:請填寫代碼內容。" 89 | }, 90 | htmlEntities : { 91 | title : "HTML實體字符" 92 | }, 93 | help : { 94 | title : "使用幫助" 95 | } 96 | } 97 | }; 98 | 99 | exports.defaults.lang = lang; 100 | }; 101 | 102 | // CommonJS/Node.js 103 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 104 | { 105 | module.exports = factory; 106 | } 107 | else if (typeof define === "function") // AMD/CMD/Sea.js 108 | { 109 | if (define.amd) { // for Require.js 110 | 111 | define(["editormd"], function(editormd) { 112 | factory(editormd); 113 | }); 114 | 115 | } else { // for Sea.js 116 | define(function(require) { 117 | var editormd = require("../editormd"); 118 | factory(editormd); 119 | }); 120 | } 121 | } 122 | else 123 | { 124 | factory(window.editormd); 125 | } 126 | 127 | })(); -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/lib/codemirror/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 by Marijn Haverbeke and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/lib/codemirror/README.md: -------------------------------------------------------------------------------- 1 | # CodeMirror 2 | [![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror) 3 | [![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror) 4 | [Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png)](https://marijnhaverbeke.nl/fund/) 5 | 6 | CodeMirror is a JavaScript component that provides a code editor in 7 | the browser. When a mode is available for the language you are coding 8 | in, it will color your code, and optionally help with indentation. 9 | 10 | The project page is http://codemirror.net 11 | The manual is at http://codemirror.net/doc/manual.html 12 | The contributing guidelines are in [CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md) 13 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/lib/codemirror/codemirror.min.css: -------------------------------------------------------------------------------- 1 | /* CodeMirror v5.0 | CodeMirror, copyright (c) by Marijn Haverbeke and others | Distributed under an MIT license: http://codemirror.net/LICENSE */ 2 | .CodeMirror{font-family:monospace;height:300px;color:black}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{background-color:white}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-guttermarker{color:black}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror div.CodeMirror-cursor{border-left:1px solid black}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.CodeMirror.cm-fat-cursor div.CodeMirror-cursor{width:auto;border:0;background:#7e7}.CodeMirror.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite}@-moz-keyframes blink{0%{background:#7e7}50%{background:0}100%{background:#7e7}}@-webkit-keyframes blink{0%{background:#7e7}50%{background:0}100%{background:#7e7}}@keyframes blink{0%{background:#7e7}50%{background:0}100%{background:#7e7}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta{color:#555}.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:bold}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-error{color:#f00}.cm-invalidchar{color:#f00}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:white}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-sizer{position:relative;border-right:30px solid transparent;-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-vscrollbar,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-gutter-filler{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;-moz-box-sizing:content-box;box-sizing:content-box;display:inline-block;margin-bottom:-30px;*zoom:1;*display:inline}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;height:100%}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper{-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-measure pre{position:static}.CodeMirror div.CodeMirror-cursor{position:absolute;border-right:0;width:0}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror ::selection{background:#d7d4f0}.CodeMirror ::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.CodeMirror span{*vertical-align:text-bottom}.cm-force-border{padding-right:.1px} 3 | @media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0} -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/lib/jquery.flowchart.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery.flowchart.js v1.1.0 | jquery.flowchart.min.js | jQuery plugin for flowchart.js. | MIT License | By: Pandao | https://github.com/pandao/jquery.flowchart.js | 2015-03-09 */ 2 | (function(factory){if(typeof require==="function"&&typeof exports==="object"&&typeof module==="object"){module.exports=factory}else{if(typeof define==="function"){factory(jQuery,flowchart)}else{factory($,flowchart)}}}(function(jQuery,flowchart){(function($){$.fn.flowChart=function(options){options=options||{};var defaults={"x":0,"y":0,"line-width":2,"line-length":50,"text-margin":10,"font-size":14,"font-color":"black","line-color":"black","element-color":"black","fill":"white","yes-text":"yes","no-text":"no","arrow-end":"block","symbols":{"start":{"font-color":"black","element-color":"black","fill":"white"},"end":{"class":"end-element"}},"flowstate":{"past":{"fill":"#CCCCCC","font-size":12},"current":{"fill":"black","font-color":"white","font-weight":"bold"},"future":{"fill":"white"},"request":{"fill":"blue"},"invalid":{"fill":"#444444"},"approved":{"fill":"#58C4A3","font-size":12,"yes-text":"APPROVED","no-text":"n/a"},"rejected":{"fill":"#C45879","font-size":12,"yes-text":"n/a","no-text":"REJECTED"}}};return this.each(function(){var $this=$(this);var diagram=flowchart.parse($this.text());var settings=$.extend(true,defaults,options);$this.html("");diagram.drawSVG(this,settings)})}})(jQuery)})); -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/lib/plugins/help-dialog/help-dialog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Help dialog plugin for Editor.md 3 | * 4 | * @file help-dialog.js 5 | * @author pandao 6 | * @version 1.2.0 7 | * @updateTime 2015-03-08 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var $ = jQuery; 17 | var pluginName = "help-dialog"; 18 | 19 | exports.fn.helpDialog = function() { 20 | var _this = this; 21 | var lang = this.lang; 22 | var editor = this.editor; 23 | var settings = this.settings; 24 | var path = settings.pluginPath + pluginName + "/"; 25 | var classPrefix = this.classPrefix; 26 | var dialogName = classPrefix + pluginName, dialog; 27 | var dialogLang = lang.dialog.help; 28 | 29 | if (editor.find("." + dialogName).length < 1) 30 | { 31 | var dialogContent = "
"; 32 | 33 | dialog = this.createDialog({ 34 | name : dialogName, 35 | title : dialogLang.title, 36 | width : 840, 37 | height : 540, 38 | mask : settings.dialogShowMask, 39 | drag : settings.dialogDraggable, 40 | content : dialogContent, 41 | lockScreen : settings.dialogLockScreen, 42 | maskStyle : { 43 | opacity : settings.dialogMaskOpacity, 44 | backgroundColor : settings.dialogMaskBgColor 45 | }, 46 | buttons : { 47 | close : [lang.buttons.close, function() { 48 | this.hide().lockScreen(false).hideMask(); 49 | 50 | return false; 51 | }] 52 | } 53 | }); 54 | } 55 | 56 | dialog = editor.find("." + dialogName); 57 | 58 | this.dialogShowMask(dialog); 59 | this.dialogLockScreen(); 60 | dialog.show(); 61 | 62 | var helpContent = dialog.find(".markdown-body"); 63 | 64 | if (helpContent.html() === "") 65 | { 66 | $.get(path + "help.md", function(text) { 67 | var md = exports.$marked(text); 68 | helpContent.html(md); 69 | 70 | helpContent.find("a").attr("target", "_blank"); 71 | }); 72 | } 73 | }; 74 | 75 | }; 76 | 77 | // CommonJS/Node.js 78 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 79 | { 80 | module.exports = factory; 81 | } 82 | else if (typeof define === "function") // AMD/CMD/Sea.js 83 | { 84 | if (define.amd) { // for Require.js 85 | 86 | define(["editormd"], function(editormd) { 87 | factory(editormd); 88 | }); 89 | 90 | } else { // for Sea.js 91 | define(function(require) { 92 | var editormd = require("./../../editormd"); 93 | factory(editormd); 94 | }); 95 | } 96 | } 97 | else 98 | { 99 | factory(window.editormd); 100 | } 101 | 102 | })(); 103 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/lib/plugins/link-dialog/link-dialog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Link dialog plugin for Editor.md 3 | * 4 | * @file link-dialog.js 5 | * @author pandao 6 | * @version 1.2.1 7 | * @updateTime 2015-06-09 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var pluginName = "link-dialog"; 17 | 18 | exports.fn.linkDialog = function() { 19 | 20 | var _this = this; 21 | var cm = this.cm; 22 | var editor = this.editor; 23 | var settings = this.settings; 24 | var selection = cm.getSelection(); 25 | var lang = this.lang; 26 | var linkLang = lang.dialog.link; 27 | var classPrefix = this.classPrefix; 28 | var dialogName = classPrefix + pluginName, dialog; 29 | 30 | cm.focus(); 31 | 32 | if (editor.find("." + dialogName).length > 0) 33 | { 34 | dialog = editor.find("." + dialogName); 35 | dialog.find("[data-url]").val("http://"); 36 | dialog.find("[data-title]").val(selection); 37 | 38 | this.dialogShowMask(dialog); 39 | this.dialogLockScreen(); 40 | dialog.show(); 41 | } 42 | else 43 | { 44 | var dialogHTML = "
" + 45 | "" + 46 | "" + 47 | "
" + 48 | "" + 49 | "" + 50 | "
" + 51 | "
"; 52 | 53 | dialog = this.createDialog({ 54 | title : linkLang.title, 55 | width : 380, 56 | height : 211, 57 | content : dialogHTML, 58 | mask : settings.dialogShowMask, 59 | drag : settings.dialogDraggable, 60 | lockScreen : settings.dialogLockScreen, 61 | maskStyle : { 62 | opacity : settings.dialogMaskOpacity, 63 | backgroundColor : settings.dialogMaskBgColor 64 | }, 65 | buttons : { 66 | enter : [lang.buttons.enter, function() { 67 | var url = this.find("[data-url]").val(); 68 | var title = this.find("[data-title]").val(); 69 | 70 | if (url === "http://" || url === "") 71 | { 72 | alert(linkLang.urlEmpty); 73 | return false; 74 | } 75 | 76 | /*if (title === "") 77 | { 78 | alert(linkLang.titleEmpty); 79 | return false; 80 | }*/ 81 | 82 | var str = "[" + title + "](" + url + " \"" + title + "\")"; 83 | 84 | if (title == "") 85 | { 86 | str = "[" + url + "](" + url + ")"; 87 | } 88 | 89 | cm.replaceSelection(str); 90 | 91 | this.hide().lockScreen(false).hideMask(); 92 | 93 | return false; 94 | }], 95 | 96 | cancel : [lang.buttons.cancel, function() { 97 | this.hide().lockScreen(false).hideMask(); 98 | 99 | return false; 100 | }] 101 | } 102 | }); 103 | } 104 | }; 105 | 106 | }; 107 | 108 | // CommonJS/Node.js 109 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 110 | { 111 | module.exports = factory; 112 | } 113 | else if (typeof define === "function") // AMD/CMD/Sea.js 114 | { 115 | if (define.amd) { // for Require.js 116 | 117 | define(["editormd"], function(editormd) { 118 | factory(editormd); 119 | }); 120 | 121 | } else { // for Sea.js 122 | define(function(require) { 123 | var editormd = require("./../../editormd"); 124 | factory(editormd); 125 | }); 126 | } 127 | } 128 | else 129 | { 130 | factory(window.editormd); 131 | } 132 | 133 | })(); 134 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/plugins/goto-line-dialog/goto-line-dialog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Goto line dialog plugin for Editor.md 3 | * 4 | * @file goto-line-dialog.js 5 | * @author pandao 6 | * @version 1.2.1 7 | * @updateTime 2015-06-09 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var $ = jQuery; 17 | var pluginName = "goto-line-dialog"; 18 | 19 | var langs = { 20 | "zh-cn" : { 21 | toolbar : { 22 | "goto-line" : "跳转到行" 23 | }, 24 | dialog : { 25 | "goto-line" : { 26 | title : "跳转到行", 27 | label : "请输入行号", 28 | error : "错误:" 29 | } 30 | } 31 | }, 32 | "zh-tw" : { 33 | toolbar : { 34 | "goto-line" : "跳轉到行" 35 | }, 36 | dialog : { 37 | "goto-line" : { 38 | title : "跳轉到行", 39 | label : "請輸入行號", 40 | error : "錯誤:" 41 | } 42 | } 43 | }, 44 | "en" : { 45 | toolbar : { 46 | "goto-line" : "Goto line" 47 | }, 48 | dialog : { 49 | "goto-line" : { 50 | title : "Goto line", 51 | label : "Enter a line number, range ", 52 | error : "Error: " 53 | } 54 | } 55 | } 56 | }; 57 | 58 | exports.fn.gotoLineDialog = function() { 59 | var _this = this; 60 | var cm = this.cm; 61 | var editor = this.editor; 62 | var settings = this.settings; 63 | var path = settings.pluginPath + pluginName +"/"; 64 | var classPrefix = this.classPrefix; 65 | var dialogName = classPrefix + pluginName, dialog; 66 | 67 | $.extend(true, this.lang, langs[this.lang.name]); 68 | this.setToolbar(); 69 | 70 | var lang = this.lang; 71 | var dialogLang = lang.dialog["goto-line"]; 72 | var lineCount = cm.lineCount(); 73 | 74 | dialogLang.error += dialogLang.label + " 1-" + lineCount; 75 | 76 | if (editor.find("." + dialogName).length < 1) 77 | { 78 | var dialogContent = [ 79 | "
", 80 | "

" + dialogLang.label + " 1-" + lineCount +"   

", 81 | "
" 82 | ].join("\n"); 83 | 84 | dialog = this.createDialog({ 85 | name : dialogName, 86 | title : dialogLang.title, 87 | width : 400, 88 | height : 180, 89 | mask : settings.dialogShowMask, 90 | drag : settings.dialogDraggable, 91 | content : dialogContent, 92 | lockScreen : settings.dialogLockScreen, 93 | maskStyle : { 94 | opacity : settings.dialogMaskOpacity, 95 | backgroundColor : settings.dialogMaskBgColor 96 | }, 97 | buttons : { 98 | enter : [lang.buttons.enter, function() { 99 | var line = parseInt(this.find("[data-line-number]").val()); 100 | 101 | if (line < 1 || line > lineCount) { 102 | alert(dialogLang.error); 103 | 104 | return false; 105 | } 106 | 107 | _this.gotoLine(line); 108 | 109 | this.hide().lockScreen(false).hideMask(); 110 | 111 | return false; 112 | }], 113 | 114 | cancel : [lang.buttons.cancel, function() { 115 | this.hide().lockScreen(false).hideMask(); 116 | 117 | return false; 118 | }] 119 | } 120 | }); 121 | } 122 | 123 | dialog = editor.find("." + dialogName); 124 | 125 | this.dialogShowMask(dialog); 126 | this.dialogLockScreen(); 127 | dialog.show(); 128 | }; 129 | 130 | }; 131 | 132 | // CommonJS/Node.js 133 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 134 | { 135 | module.exports = factory; 136 | } 137 | else if (typeof define === "function") // AMD/CMD/Sea.js 138 | { 139 | if (define.amd) { // for Require.js 140 | 141 | define(["editormd"], function(editormd) { 142 | factory(editormd); 143 | }); 144 | 145 | } else { // for Sea.js 146 | define(function(require) { 147 | var editormd = require("./../../editormd"); 148 | factory(editormd); 149 | }); 150 | } 151 | } 152 | else 153 | { 154 | factory(window.editormd); 155 | } 156 | 157 | })(); 158 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/plugins/help-dialog/help-dialog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Help dialog plugin for Editor.md 3 | * 4 | * @file help-dialog.js 5 | * @author pandao 6 | * @version 1.2.0 7 | * @updateTime 2015-03-08 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var $ = jQuery; 17 | var pluginName = "help-dialog"; 18 | 19 | exports.fn.helpDialog = function() { 20 | var _this = this; 21 | var lang = this.lang; 22 | var editor = this.editor; 23 | var settings = this.settings; 24 | var path = settings.pluginPath + pluginName + "/"; 25 | var classPrefix = this.classPrefix; 26 | var dialogName = classPrefix + pluginName, dialog; 27 | var dialogLang = lang.dialog.help; 28 | 29 | if (editor.find("." + dialogName).length < 1) 30 | { 31 | var dialogContent = "
"; 32 | 33 | dialog = this.createDialog({ 34 | name : dialogName, 35 | title : dialogLang.title, 36 | width : 840, 37 | height : 540, 38 | mask : settings.dialogShowMask, 39 | drag : settings.dialogDraggable, 40 | content : dialogContent, 41 | lockScreen : settings.dialogLockScreen, 42 | maskStyle : { 43 | opacity : settings.dialogMaskOpacity, 44 | backgroundColor : settings.dialogMaskBgColor 45 | }, 46 | buttons : { 47 | close : [lang.buttons.close, function() { 48 | this.hide().lockScreen(false).hideMask(); 49 | 50 | return false; 51 | }] 52 | } 53 | }); 54 | } 55 | 56 | dialog = editor.find("." + dialogName); 57 | 58 | this.dialogShowMask(dialog); 59 | this.dialogLockScreen(); 60 | dialog.show(); 61 | 62 | var helpContent = dialog.find(".markdown-body"); 63 | 64 | if (helpContent.html() === "") 65 | { 66 | $.get(path + "help.md", function(text) { 67 | var md = exports.$marked(text); 68 | helpContent.html(md); 69 | 70 | helpContent.find("a").attr("target", "_blank"); 71 | }); 72 | } 73 | }; 74 | 75 | }; 76 | 77 | // CommonJS/Node.js 78 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 79 | { 80 | module.exports = factory; 81 | } 82 | else if (typeof define === "function") // AMD/CMD/Sea.js 83 | { 84 | if (define.amd) { // for Require.js 85 | 86 | define(["editormd"], function(editormd) { 87 | factory(editormd); 88 | }); 89 | 90 | } else { // for Sea.js 91 | define(function(require) { 92 | var editormd = require("./../../editormd"); 93 | factory(editormd); 94 | }); 95 | } 96 | } 97 | else 98 | { 99 | factory(window.editormd); 100 | } 101 | 102 | })(); 103 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/plugins/link-dialog/link-dialog.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Link dialog plugin for Editor.md 3 | * 4 | * @file link-dialog.js 5 | * @author pandao 6 | * @version 1.2.1 7 | * @updateTime 2015-06-09 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var pluginName = "link-dialog"; 17 | 18 | exports.fn.linkDialog = function() { 19 | 20 | var _this = this; 21 | var cm = this.cm; 22 | var editor = this.editor; 23 | var settings = this.settings; 24 | var selection = cm.getSelection(); 25 | var lang = this.lang; 26 | var linkLang = lang.dialog.link; 27 | var classPrefix = this.classPrefix; 28 | var dialogName = classPrefix + pluginName, dialog; 29 | 30 | cm.focus(); 31 | 32 | if (editor.find("." + dialogName).length > 0) 33 | { 34 | dialog = editor.find("." + dialogName); 35 | dialog.find("[data-url]").val("http://"); 36 | dialog.find("[data-title]").val(selection); 37 | 38 | this.dialogShowMask(dialog); 39 | this.dialogLockScreen(); 40 | dialog.show(); 41 | } 42 | else 43 | { 44 | var dialogHTML = "
" + 45 | "" + 46 | "" + 47 | "
" + 48 | "" + 49 | "" + 50 | "
" + 51 | "
"; 52 | 53 | dialog = this.createDialog({ 54 | title : linkLang.title, 55 | width : 380, 56 | height : 211, 57 | content : dialogHTML, 58 | mask : settings.dialogShowMask, 59 | drag : settings.dialogDraggable, 60 | lockScreen : settings.dialogLockScreen, 61 | maskStyle : { 62 | opacity : settings.dialogMaskOpacity, 63 | backgroundColor : settings.dialogMaskBgColor 64 | }, 65 | buttons : { 66 | enter : [lang.buttons.enter, function() { 67 | var url = this.find("[data-url]").val(); 68 | var title = this.find("[data-title]").val(); 69 | 70 | if (url === "http://" || url === "") 71 | { 72 | alert(linkLang.urlEmpty); 73 | return false; 74 | } 75 | 76 | /*if (title === "") 77 | { 78 | alert(linkLang.titleEmpty); 79 | return false; 80 | }*/ 81 | 82 | var str = "[" + title + "](" + url + " \"" + title + "\")"; 83 | 84 | if (title == "") 85 | { 86 | str = "[" + url + "](" + url + ")"; 87 | } 88 | 89 | cm.replaceSelection(str); 90 | 91 | this.hide().lockScreen(false).hideMask(); 92 | 93 | return false; 94 | }], 95 | 96 | cancel : [lang.buttons.cancel, function() { 97 | this.hide().lockScreen(false).hideMask(); 98 | 99 | return false; 100 | }] 101 | } 102 | }); 103 | } 104 | }; 105 | 106 | }; 107 | 108 | // CommonJS/Node.js 109 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 110 | { 111 | module.exports = factory; 112 | } 113 | else if (typeof define === "function") // AMD/CMD/Sea.js 114 | { 115 | if (define.amd) { // for Require.js 116 | 117 | define(["editormd"], function(editormd) { 118 | factory(editormd); 119 | }); 120 | 121 | } else { // for Sea.js 122 | define(function(require) { 123 | var editormd = require("./../../editormd"); 124 | factory(editormd); 125 | }); 126 | } 127 | } 128 | else 129 | { 130 | factory(window.editormd); 131 | } 132 | 133 | })(); 134 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/plugins/plugin-template.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Link dialog plugin for Editor.md 3 | * 4 | * @file link-dialog.js 5 | * @author pandao 6 | * @version 1.2.0 7 | * @updateTime 2015-03-07 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var $ = jQuery; // if using module loader(Require.js/Sea.js). 17 | 18 | var langs = { 19 | "zh-cn" : { 20 | toolbar : { 21 | table : "表格" 22 | }, 23 | dialog : { 24 | table : { 25 | title : "添加表格", 26 | cellsLabel : "单元格数", 27 | alignLabel : "对齐方式", 28 | rows : "行数", 29 | cols : "列数", 30 | aligns : ["默认", "左对齐", "居中对齐", "右对齐"] 31 | } 32 | } 33 | }, 34 | "zh-tw" : { 35 | toolbar : { 36 | table : "添加表格" 37 | }, 38 | dialog : { 39 | table : { 40 | title : "添加表格", 41 | cellsLabel : "單元格數", 42 | alignLabel : "對齊方式", 43 | rows : "行數", 44 | cols : "列數", 45 | aligns : ["默認", "左對齊", "居中對齊", "右對齊"] 46 | } 47 | } 48 | }, 49 | "en" : { 50 | toolbar : { 51 | table : "Tables" 52 | }, 53 | dialog : { 54 | table : { 55 | title : "Tables", 56 | cellsLabel : "Cells", 57 | alignLabel : "Align", 58 | rows : "Rows", 59 | cols : "Cols", 60 | aligns : ["Default", "Left align", "Center align", "Right align"] 61 | } 62 | } 63 | } 64 | }; 65 | 66 | exports.fn.htmlEntities = function() { 67 | /* 68 | var _this = this; // this == the current instance object of Editor.md 69 | var lang = _this.lang; 70 | var settings = _this.settings; 71 | var editor = this.editor; 72 | var cursor = cm.getCursor(); 73 | var selection = cm.getSelection(); 74 | var classPrefix = this.classPrefix; 75 | 76 | $.extend(true, this.lang, langs[this.lang.name]); // l18n 77 | this.setToolbar(); 78 | 79 | cm.focus(); 80 | */ 81 | //.... 82 | }; 83 | 84 | }; 85 | 86 | // CommonJS/Node.js 87 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 88 | { 89 | module.exports = factory; 90 | } 91 | else if (typeof define === "function") // AMD/CMD/Sea.js 92 | { 93 | if (define.amd) { // for Require.js 94 | 95 | define(["editormd"], function(editormd) { 96 | factory(editormd); 97 | }); 98 | 99 | } else { // for Sea.js 100 | define(function(require) { 101 | var editormd = require("./../../editormd"); 102 | factory(editormd); 103 | }); 104 | } 105 | } 106 | else 107 | { 108 | factory(window.editormd); 109 | } 110 | 111 | })(); 112 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/editormd/plugins/test-plugin/test-plugin.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Test plugin for Editor.md 3 | * 4 | * @file test-plugin.js 5 | * @author pandao 6 | * @version 1.2.0 7 | * @updateTime 2015-03-07 8 | * {@link https://github.com/pandao/editor.md} 9 | * @license MIT 10 | */ 11 | 12 | (function() { 13 | 14 | var factory = function (exports) { 15 | 16 | var $ = jQuery; // if using module loader(Require.js/Sea.js). 17 | 18 | exports.testPlugin = function(){ 19 | alert("testPlugin"); 20 | }; 21 | 22 | exports.fn.testPluginMethodA = function() { 23 | /* 24 | var _this = this; // this == the current instance object of Editor.md 25 | var lang = _this.lang; 26 | var settings = _this.settings; 27 | var editor = this.editor; 28 | var cursor = cm.getCursor(); 29 | var selection = cm.getSelection(); 30 | var classPrefix = this.classPrefix; 31 | 32 | cm.focus(); 33 | */ 34 | //.... 35 | 36 | alert("testPluginMethodA"); 37 | }; 38 | 39 | }; 40 | 41 | // CommonJS/Node.js 42 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") 43 | { 44 | module.exports = factory; 45 | } 46 | else if (typeof define === "function") // AMD/CMD/Sea.js 47 | { 48 | if (define.amd) { // for Require.js 49 | 50 | define(["editormd"], function(editormd) { 51 | factory(editormd); 52 | }); 53 | 54 | } else { // for Sea.js 55 | define(function(require) { 56 | var editormd = require("./../../editormd"); 57 | factory(editormd); 58 | }); 59 | } 60 | } 61 | else 62 | { 63 | factory(window.editormd); 64 | } 65 | 66 | })(); 67 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/sidedrawer.js: -------------------------------------------------------------------------------- 1 | jQuery(function($) { 2 | 'use strict'; 3 | var $bodyEl = $('body'), 4 | $sidedrawerEl = $('#sidedrawer'); 5 | 6 | function showSidedrawer() { 7 | // show overlay 8 | var options = { 9 | onclose: function() { 10 | $sidedrawerEl 11 | .removeClass('active') 12 | .appendTo(document.body); 13 | } 14 | }; 15 | 16 | var $overlayEl = $(mui.overlay('on', options)); 17 | 18 | // show element 19 | $sidedrawerEl.appendTo($overlayEl); 20 | setTimeout(function() { 21 | $sidedrawerEl.addClass('active'); 22 | }, 20); 23 | } 24 | 25 | function hideSidedrawer() { 26 | $bodyEl.toggleClass('hide-sidedrawer'); 27 | } 28 | 29 | $('.js-show-sidedrawer').on('click', showSidedrawer); 30 | $('.js-hide-sidedrawer').on('click', hideSidedrawer); 31 | 32 | /** 33 | * Expand the folder and load the list of subpages 34 | */ 35 | var $titleEls = $('.folder-expander', $sidedrawerEl); 36 | 37 | $titleEls 38 | .next() 39 | .hide(); 40 | 41 | $titleEls.on('click', folder_expaner_onclick); 42 | 43 | /** 44 | * Assigns onclick events to matched elements. 45 | */ 46 | function folder_expaner_onclick ($ev) { 47 | $ev.stopPropagation(); 48 | $ev.preventDefault(); 49 | let $parent = $(this).parent().parent(); 50 | let $submenu = $('ul', $parent); 51 | // if the list is already retreived, just toggle 52 | if($submenu.get(0) !== undefined) { 53 | $('ul', $parent).slideToggle(200); 54 | return; 55 | } 56 | let $api_url = $(this).attr('href'); 57 | get_страници($api_url,$parent); 58 | $submenu.slideToggle(200); 59 | } 60 | 61 | /** 62 | * Gets the list of pages from $url and appends them to $parent. 63 | */ 64 | function get_страници ($url, $parent) { 65 | let $url_no_qs = $url.split(/[?]/)[0]; 66 | $.getJSON($url).done(function($data){ 67 | if($data.length === 0) { 68 | let $self_href = $('a:first-child', $parent).attr('href'); 69 | $parent.append(``); 70 | return; 71 | } 72 | $('
    ').appendTo($parent); 73 | $data.forEach(function($row){ 74 | let $page_url = `/${$row.alias}.${$row.language}.html`; 75 | let $page_link = `${$row.title}`; 76 | let $expander_url = '', 77 | $expander_link = '', 78 | $item = `
    ${$page_link}${$expander_link}
    `; 79 | if($row.is_dir === 1) { 80 | $expander_url = $url_no_qs + '?' 81 | + $.param({pid: $row.id, 'lang': $row.language}); 82 | $expander_link = ``; 84 | $item = `${$page_link}${$expander_link}`; 85 | let $li = $('ul', $parent).append(`
  • ${$item}
  • `); 86 | $(`#id${$row.id}`, $li).on('click', folder_expaner_onclick); 87 | return; 88 | } 89 | $('ul', $parent).append(`
  • ${$item}
  • `); 90 | }); 91 | }); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/langs/bg.min.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bg.js 3 | * Bulgarian translation for Trumbowyg 4 | * http://alex-d.github.com/Trumbowyg 5 | * =========================================================== 6 | * Author : Aleksandar Dimitrov 7 | */ 8 | jQuery.trumbowyg.langs.bg={viewHTML:"Прегледай HTML",formatting:"Форматиране",p:"Параграф",blockquote:"Цитат",code:"Код",header:"Заглавие",bold:"Удебелен",italic:"Наклонен",strikethrough:"Зачеркнат",underline:"Подчертан",strong:"Удебелен",em:"Наклонен",del:"Зачеркнат",unorderedList:"Обикновен списък",orderedList:"Номериран списък",insertImage:"Добави изображение",insertVideo:"Добави видео",link:"Връзка",createLink:"Създай връзка",unlink:"Премахни връзката",justifyLeft:"Подравни от ляво",justifyCenter:"Центрирай",justifyRight:"Подравни от дясно",justifyFull:"Подравни по ширина",horizontalRule:"Хоризонтална линия",fullscreen:"На цял екран",close:"Затвори",submit:"Впиши",reset:"Отмени",required:"Задължително",description:"Описание",title:"Заглавие",text:"Текст"}; -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/langs/cs.min.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * cs.js 3 | * Czech translation for Trumbowyg 4 | * http://alex-d.github.com/Trumbowyg 5 | * =========================================================== 6 | * Author : Jan Svoboda (https://github.com/svoboda-jan) 7 | */ 8 | jQuery.trumbowyg.langs.cs={viewHTML:"Zobrazit HTML",formatting:"Formátování",p:"Odstavec",blockquote:"Citace",code:"Kód",header:"Nadpis",bold:"Tučné",italic:"Kurzíva",strikethrough:"Přeškrtnuté",underline:"Podtržené",strong:"Tučné",em:"Zvýraznit",del:"Smazat",unorderedList:"Netříděný seznam",orderedList:"Tříděný seznam",insertImage:"Vložit obrázek",insertVideo:"Vložit video",link:"Odkaz",createLink:"Vložit odkaz",unlink:"Smazat odkaz",justifyLeft:"Zarovnat doleva",justifyCenter:"Zarovnat na střed",justifyRight:"Zarovnat doprava",justifyFull:"Zarovnat do bloku",horizontalRule:"Vložit vodorovnou čáru",fullscreen:"Režim celé obrazovky",close:"Zavřít",submit:"Potvrdit",reset:"Zrušit",required:"Povinné",description:"Popis",title:"Nadpis",text:"Text"}; -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/langs/pl.min.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * pl.js 3 | * Polish translation for Trumbowyg 4 | * http://alex-d.github.com/Trumbowyg 5 | * =========================================================== 6 | * Author : Paweł Abramowicz 7 | * Github : https://github.com/pawelabrams 8 | */ 9 | jQuery.trumbowyg.langs.pl={viewHTML:"Pokaż HTML",formatting:"Format",p:"Akapit",blockquote:"Cytat",code:"Kod",header:"Nagłówek",bold:"Pogrubienie",italic:"Pochylenie",strikethrough:"Przekreślenie",underline:"Podkreślenie",strong:"Wytłuszczenie",em:"Uwydatnienie",del:"Usunięte",unorderedList:"Lista nieuporządkowana",orderedList:"Lista uporządkowana",insertImage:"Wstaw obraz",insertVideo:"Wstaw film",link:"Link",createLink:"Wstaw link",unlink:"Usuń link",justifyLeft:"Wyrównaj do lewej",justifyCenter:"Wyśrodkuj",justifyRight:"Wyrównaj do prawej",justifyFull:"Wyjustuj",horizontalRule:"Odkreśl linią",fullscreen:"Pełny ekran",close:"Zamknij",submit:"Zastosuj",reset:"Przywróć",required:"Wymagane",description:"Opis",title:"Tytuł",text:"Tekst"}; -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/langs/ru.min.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * ru.js 3 | * Russion translation for Trumbowyg 4 | * http://alex-d.github.com/Trumbowyg 5 | * =========================================================== 6 | * Author : Yuri Lya 7 | */ 8 | jQuery.trumbowyg.langs.ru={viewHTML:"Посмотреть HTML",undo:"Отменить",redo:"Повторить",formatting:"Форматирование",p:"Обычный",blockquote:"Цитата",code:"Код",header:"Заголовок",bold:"Полужирный",italic:"Курсив",strikethrough:"Зачеркнутый",underline:"Подчеркнутый",strong:"Полужирный",em:"Курсив",del:"Зачеркнутый",superscript:"Надстрочный",subscript:"Подстрочный",unorderedList:"Обычный список",orderedList:"Нумерованный список",insertImage:"Вставить изображение",insertVideo:"Вставить видео",link:"Ссылка",createLink:"Вставить ссылку",unlink:"Удалить ссылку",justifyLeft:"По левому краю",justifyCenter:"По центру",justifyRight:"По правому краю",justifyFull:"По ширине",horizontalRule:"Горизонтальная линия",removeformat:"Очистить форматирование",fullscreen:"Во весь экран",close:"Закрыть",submit:"Вставить",reset:"Отменить",required:"Обязательное",description:"Описание",title:"Подсказка",text:"Текст"}; -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/plugins/allowtagsfrompaste/trumbowyg.allowtagsfrompaste.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * trumbowyg.allowTagsFromPaste.js v1.0.2 3 | * It cleans tags from pasted text, whilst allowing several specified tags 4 | * http://alex-d.github.com/Trumbowyg 5 | * =========================================================== 6 | * Author : Fathi Anshory (0x00000F5C) 7 | * Twitter : @fscchannl 8 | * Notes: 9 | * - removeformatPasted must be set to FALSE since it was applied prior to pasteHandlers, or else it will be useless 10 | * - It is most advisable to use along with the cleanpaste plugin, or else you'd end up with dirty markup 11 | */ 12 | 13 | (function ($) { 14 | 'use strict'; 15 | 16 | var defaultOptions = { 17 | // When empty, all tags are allowed making this plugin useless 18 | // If you want to remove all tags, use removeformatPasted core option instead 19 | allowedTags: [], 20 | // List of tags which can be allowed 21 | removableTags: [ 22 | 'a', 23 | 'abbr', 24 | 'address', 25 | 'b', 26 | 'bdi', 27 | 'bdo', 28 | 'blockquote', 29 | 'br', 30 | 'cite', 31 | 'code', 32 | 'del', 33 | 'dfn', 34 | 'details', 35 | 'em', 36 | 'h1', 37 | 'h2', 38 | 'h3', 39 | 'h4', 40 | 'h5', 41 | 'h6', 42 | 'hr', 43 | 'i', 44 | 'ins', 45 | 'kbd', 46 | 'mark', 47 | 'meter', 48 | 'pre', 49 | 'progress', 50 | 'q', 51 | 'rp', 52 | 'rt', 53 | 'ruby', 54 | 's', 55 | 'samp', 56 | 'small', 57 | 'span', 58 | 'strong', 59 | 'sub', 60 | 'summary', 61 | 'sup', 62 | 'time', 63 | 'u', 64 | 'var', 65 | 'wbr', 66 | 'img', 67 | 'map', 68 | 'area', 69 | 'canvas', 70 | 'figcaption', 71 | 'figure', 72 | 'picture', 73 | 'audio', 74 | 'source', 75 | 'track', 76 | 'video', 77 | 'ul', 78 | 'ol', 79 | 'li', 80 | 'dl', 81 | 'dt', 82 | 'dd', 83 | 'table', 84 | 'caption', 85 | 'th', 86 | 'tr', 87 | 'td', 88 | 'thead', 89 | 'tbody', 90 | 'tfoot', 91 | 'col', 92 | 'colgroup', 93 | 'style', 94 | 'div', 95 | 'p', 96 | 'form', 97 | 'input', 98 | 'textarea', 99 | 'button', 100 | 'select', 101 | 'optgroup', 102 | 'option', 103 | 'label', 104 | 'fieldset', 105 | 'legend', 106 | 'datalist', 107 | 'keygen', 108 | 'output', 109 | 'iframe', 110 | 'link', 111 | 'nav', 112 | 'header', 113 | 'hgroup', 114 | 'footer', 115 | 'main', 116 | 'section', 117 | 'article', 118 | 'aside', 119 | 'dialog', 120 | 'script', 121 | 'noscript', 122 | 'embed', 123 | 'object', 124 | 'param' 125 | ] 126 | }; 127 | 128 | $.extend(true, $.trumbowyg, { 129 | plugins: { 130 | allowTagsFromPaste: { 131 | init: function (trumbowyg) { 132 | // Force disable remove format pasted 133 | trumbowyg.o.removeformatPasted = false; 134 | 135 | if (!trumbowyg.o.plugins.allowTagsFromPaste) { 136 | return; 137 | } 138 | 139 | var allowedTags = trumbowyg.o.plugins.allowTagsFromPaste.allowedTags || defaultOptions.allowedTags; 140 | var removableTags = trumbowyg.o.plugins.allowTagsFromPaste.removableTags || defaultOptions.removableTags; 141 | 142 | if (allowedTags.length === 0) { 143 | return; 144 | } 145 | 146 | // Get list of tags to remove 147 | var tagsToRemove = $(removableTags).not(allowedTags).get(); 148 | 149 | trumbowyg.pasteHandlers.push(function () { 150 | setTimeout(function () { 151 | var processNodes = trumbowyg.$ed.html(); 152 | $.each(tagsToRemove, function (iterator, tagName) { 153 | processNodes = processNodes.replace(new RegExp('<\\/?' + tagName + '(\\s[^>]*)?>', 'gi'), ''); 154 | }); 155 | trumbowyg.$ed.html(processNodes); 156 | }, 0); 157 | }); 158 | } 159 | } 160 | } 161 | }); 162 | })(jQuery); 163 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/plugins/allowtagsfrompaste/trumbowyg.allowtagsfrompaste.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";var a={allowedTags:[],removableTags:["a","abbr","address","b","bdi","bdo","blockquote","br","cite","code","del","dfn","details","em","h1","h2","h3","h4","h5","h6","hr","i","ins","kbd","mark","meter","pre","progress","q","rp","rt","ruby","s","samp","small","span","strong","sub","summary","sup","time","u","var","wbr","img","map","area","canvas","figcaption","figure","picture","audio","source","track","video","ul","ol","li","dl","dt","dd","table","caption","th","tr","td","thead","tbody","tfoot","col","colgroup","style","div","p","form","input","textarea","button","select","optgroup","option","label","fieldset","legend","datalist","keygen","output","iframe","link","nav","header","hgroup","footer","main","section","article","aside","dialog","script","noscript","embed","object","param"]};e.extend(!0,e.trumbowyg,{plugins:{allowTagsFromPaste:{init:function(t){if(t.o.removeformatPasted=!1,t.o.plugins.allowTagsFromPaste){var o=t.o.plugins.allowTagsFromPaste.allowedTags||a.allowedTags,r=t.o.plugins.allowTagsFromPaste.removableTags||a.removableTags;if(0!==o.length){var s=e(r).not(o).get();t.pasteHandlers.push(function(){setTimeout(function(){var a=t.$ed.html();e.each(s,function(e,t){a=a.replace(new RegExp("<\\/?"+t+"(\\s[^>]*)?>","gi"),"")}),t.$ed.html(a)},0)})}}}}}})}(jQuery); -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/plugins/base64/trumbowyg.base64.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";var a=function(){return"undefined"!=typeof FileReader},r=function(e){return/^data:image\/[a-z]?/i.test(e)};e.extend(!0,e.trumbowyg,{langs:{en:{base64:"Image as base64",file:"File",errFileReaderNotSupported:"FileReader is not supported by your browser.",errInvalidImage:"Invalid image file."},da:{base64:"Billede som base64",file:"Fil",errFileReaderNotSupported:"FileReader er ikke understøttet af din browser.",errInvalidImage:"Ugyldig billedfil."},fr:{base64:"Image en base64",file:"Fichier"},cs:{base64:"Vložit obrázek",file:"Soubor"},zh_cn:{base64:"图片(Base64编码)",file:"文件"},nl:{base64:"Afbeelding inline",file:"Bestand",errFileReaderNotSupported:"Uw browser ondersteunt deze functionaliteit niet.",errInvalidImage:"De gekozen afbeelding is ongeldig."},ru:{base64:"Изображение как код в base64",file:"Файл",errFileReaderNotSupported:"FileReader не поддерживается вашим браузером.",errInvalidImage:"Недопустимый файл изображения."},ja:{base64:"画像 (Base64形式)",file:"ファイル",errFileReaderNotSupported:"あなたのブラウザーはFileReaderをサポートしていません",errInvalidImage:"画像形式が正しくありません"},tr:{base64:"Base64 olarak resim",file:"Dosya",errFileReaderNotSupported:"FileReader tarayıcınız tarafından desteklenmiyor.",errInvalidImage:"Geçersiz resim dosyası."},zh_tw:{base64:"圖片(base64編碼)",file:"檔案",errFileReaderNotSupported:"你的瀏覽器不支援FileReader",errInvalidImage:"不正確的檔案格式"},pt_br:{base64:"Imagem em base64",file:"Arquivo",errFileReaderNotSupported:"FileReader não é suportado pelo seu navegador.",errInvalidImage:"Arquivo de imagem inválido."},ko:{base64:"그림 넣기(base64)",file:"파일",errFileReaderNotSupported:"FileReader가 현재 브라우저를 지원하지 않습니다.",errInvalidImage:"유효하지 않은 파일"}},plugins:{base64:{shouldInit:a,init:function(i){var t={isSupported:a,fn:function(){i.saveRange();var a,t=i.openModalInsert(i.lang.base64,{file:{type:"file",required:!0,attributes:{accept:"image/*"}},alt:{label:"description",value:i.getRangeText()}},function(l){var n=new FileReader;n.onloadend=function(a){r(a.target.result)?(i.execCmd("insertImage",n.result,!1,!0),e(['img[src="',n.result,'"]:not([alt])'].join(""),i.$box).attr("alt",l.alt),i.closeModal()):i.addErrorOnModalField(e("input[type=file]",t),i.lang.errInvalidImage)},n.readAsDataURL(a)});e("input[type=file]").on("change",function(e){a=e.target.files[0]})}};i.addBtnDef("base64",t)}}}})}(jQuery); -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/plugins/template/trumbowyg.template.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 'use strict'; 3 | 4 | // Adds the language variables 5 | $.extend(true, $.trumbowyg, { 6 | langs: { 7 | en: { 8 | template: 'Template' 9 | }, 10 | da: { 11 | template: 'Skabelon' 12 | }, 13 | fr: { 14 | template: 'Patron' 15 | }, 16 | nl: { 17 | template: 'Sjabloon' 18 | }, 19 | ru: { 20 | template: 'Шаблон' 21 | }, 22 | ja: { 23 | template: 'テンプレート' 24 | }, 25 | tr: { 26 | template: 'Şablon' 27 | }, 28 | zh_tw: { 29 | template: '模板', 30 | }, 31 | pt_br: { 32 | template: 'Modelo' 33 | }, 34 | ko: { 35 | template: '서식' 36 | }, 37 | } 38 | }); 39 | 40 | // Adds the extra button definition 41 | $.extend(true, $.trumbowyg, { 42 | plugins: { 43 | template: { 44 | shouldInit: function (trumbowyg) { 45 | return trumbowyg.o.plugins.hasOwnProperty('templates'); 46 | }, 47 | init: function (trumbowyg) { 48 | trumbowyg.addBtnDef('template', { 49 | dropdown: templateSelector(trumbowyg), 50 | hasIcon: false, 51 | text: trumbowyg.lang.template 52 | }); 53 | } 54 | } 55 | } 56 | }); 57 | 58 | // Creates the template-selector dropdown. 59 | function templateSelector(trumbowyg) { 60 | var available = trumbowyg.o.plugins.templates; 61 | var templates = []; 62 | 63 | $.each(available, function (index, template) { 64 | trumbowyg.addBtnDef('template_' + index, { 65 | fn: function () { 66 | trumbowyg.html(template.html); 67 | }, 68 | hasIcon: false, 69 | title: template.name 70 | }); 71 | templates.push('template_' + index); 72 | }); 73 | 74 | return templates; 75 | } 76 | })(jQuery); 77 | -------------------------------------------------------------------------------- /lib/Slovo/resources/public/js/trumbowyg-2.18/plugins/template/trumbowyg.template.min.js: -------------------------------------------------------------------------------- 1 | !function(t){"use strict";function e(e){var n=e.o.plugins.templates,a=[];return t.each(n,function(t,n){e.addBtnDef("template_"+t,{fn:function(){e.html(n.html)},hasIcon:!1,title:n.name}),a.push("template_"+t)}),a}t.extend(!0,t.trumbowyg,{langs:{en:{template:"Template"},da:{template:"Skabelon"},fr:{template:"Patron"},nl:{template:"Sjabloon"},ru:{template:"Шаблон"},ja:{template:"テンプレート"},tr:{template:"Şablon"},zh_tw:{template:"模板"},pt_br:{template:"Modelo"},ko:{template:"서식"}}}),t.extend(!0,t.trumbowyg,{plugins:{template:{shouldInit:function(t){return t.o.plugins.hasOwnProperty("templates")},init:function(t){t.addBtnDef("template",{dropdown:e(t),hasIcon:!1,text:t.lang.template})}}}})}(jQuery); -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/auth/first_login_form.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Първи входъ'; 2 |

    <%= title %>

    3 |
    4 |
    5 | %= form_for first_login => (id => 'first_login', autocomplete =>'off', class => "mui-form") => begin 6 | % if(stash->{error_message}){ 7 |
    8 | <%= stash->{error_message} %> 9 | Ако това не помогне, помолете човека, създал вашата сметка, да направи 10 | потребителя ви „Действащ“ и използвайте 11 | <%=link_to 'временен ключь' => 'lost_password_form' %> за влизане. 12 |
    13 | %} 14 | % if (stash->{row}) { 15 |

    16 | Добре дошли за първи път в слово.бг. 17 |

    18 |

    19 | Щом се намирате на тази страница, това означава, че някой е създал сметка за 20 | вас и сте получили електронно писмо с препратката, която ви доведе тук. 21 | Моля, въведете имената на човека, създал вашата сметка, за да можете на 22 | следващата стъпка да зададете тайния си ключ за вход. Отсега нататък ще 23 | използвате него при влизане. 24 |

    25 | %= input_tag 'token' => (type =>'hidden') 26 | 27 |
    28 | %= label_for first_name =>'Име' 29 | %= text_field first_name => required => 1, size => 40, class => 'mui-textfield' 30 |
    31 |
    32 | %= label_for last_name =>'Фамилия' 33 | %= text_field last_name => required => 1, size => 40, class => 'mui-textfield' 34 |
    35 | %= submit_button 'Нататък'=> (class=>"mui-btn mui-btn--primary") 36 | % } 37 | % end 38 |
    39 |
    40 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/auth/lost_password_form.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie'; 2 | % title 'Загубен таен ключ'; 3 |
    4 |

    <%= title %>

    5 |
    6 | % if(my $mail = $c->param('email')){ 7 |

    Ако потребителят с адрес за електронна поща <%= $mail %> съществува, ще му 8 | изпратим съобщение, съдържащо временен таен ключ за вход. Използвайте този 9 | ключ, за да 10 | <%= link_to 'влезете по обичайния начин'=>'sign_in' %>. 11 |

    12 |

    Ако не получите електронно съобщение, това означава, че нямате сметка и 13 | трябва да помолите някого от съществуващите потребители да ви 14 | създаде.

    15 | % } else { 16 | %= form_for lost_password_form => (method => 'POST', class => 'mui-form') => begin 17 |
    18 | %= label_for email => 'Електронна поща' 19 | <%= 20 | text_field 'email', 21 | placeholder => 'Адрес за "Електронна поща": me@example.com', 22 | title => 'Адрес за "Електронна поща", на който да изпратим ключ за еднократно влизане.' 23 | %> 24 |
    25 | 26 | %= t 'button' => (class => 'mui-btn mui-btn--primary') => 'Изпращане' 27 | %= end 28 | % } 29 |
    30 |
    31 | 32 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/celini/create.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Нова цѣлина'; 2 | %= include 'celini/_form', caption => 'Create', target => 'store_celini' 3 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/celini/edit.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => ${id} ? 'Промѣна на цѣлина ' . ${id} : 'Нова цѣлина'; 2 | %= include 'celini/_form', caption => 'Update', target => 'update_celini' 3 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/celini/index.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Site/Celini'; 2 | 19 | 20 | 21 | % my @columns = qw(page_id id pid title language alias box data_format); #table columns to show 22 | 23 |
    24 |
    25 | 26 | 27 | 28 | % for my $column (@columns) { 29 | 30 | % } 31 | 32 | 33 | % my $title_data_type = $c->stranici->title_data_type; 34 | % for my $item (@$celini) { $item->{is_dir} = 1 if ($item->{permissions} =~ /^d/); 35 | {data_type} eq $title_data_type ? ' style="font-weight: bolder"' : ''%>> 36 | 37 | % for my $column (@columns) { 38 | % if ($column eq 'id') { 39 | 40 | % } else { 41 | 42 | % } 43 | % } 44 | 67 | 68 | % } 69 |
    <%= uc($column) %>
    <%= $item->{is_dir} ? '🗀' : '☰' %><%= link_to $item->{id} => show_celini => {id => $item->{id}} %><%= $item->{$column} %> 45 | <%= link_to '📝' => edit_celini => {id => $item->{id}} %> 46 | % if(not defined $page_id) { 47 | <%= 48 | link_to '☰' => 49 | url_with(celini_in_stranica => page_id => $item->{page_id}) => 50 | (title => 'list siblings') 51 | %> 52 | % } 53 | <%= 54 | link_to '🖹🗌' => url_with('create_celini') 55 | ->query(page_id => $item->{page_id}, sorting => ++$item->{sorting}) => 56 | (title => 'Add a sibling after this') 57 | %> 58 | % if ($item->{permissions} =~/^d/) { # is a container/directory 59 | <%= 60 | link_to '☲🗌' => url_with('create_celini') 61 | ->query( 62 | page_id => $item->{page_id}, sorting => ++$item->{sorting}, pid => $item->{id} 63 | ) => (title => 'Add new child content to this') 64 | %> 65 | % } 66 |
    70 |
    71 |
    72 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/celini/show.html.ep: -------------------------------------------------------------------------------- 1 | % my $ce = $celini; 2 | % my $title = $ce->{title}||$ce->{alias}||$ce->{id}; 3 | % layout 'upravlenie', title => 'Record from table celini: ' . $title; 4 |

    <%= title %>

    5 | <%= link_to 'Списък съ съсѣдни цѣлини' => 6 | url_with(celini_in_stranica => page_id => $ce->{page_id}), 7 | (title => '.. въ сѫщата страница') 8 | %> 9 | <%= link_to 'Добавяне на съдържанѥ въ сѫщата страница' => 10 | url_with('create_celini')->query(page_id => $ce->{'page_id'}), 11 | (title => 'Добавѭне на съдържанѥ въ сѫщата страница с ꙋказателъ '. $ce->{page_id}) %> 12 | <%= link_to 'Промѣна на страница' => 'edit_stranici' => {id => $ce->{page_id}}%> 13 | %= link_to '📝 Промѣна' => edit_celini => {id => $ce->{id}} 14 |
    15 | % for my $k (keys %$ce) { 16 | <%== 17 | t span => (id => $k, class => 'item-field') => "$k: " . ($ce->{$k} // '') 18 | %> 19 | % } 20 |
    21 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/domove/_form.html.ep: -------------------------------------------------------------------------------- 1 | %= form_for $target => begin 2 | 3 | %= label_for domain =>'Domain' 4 | %= text_field domain => (required => 1, size => 63) 5 |
    6 | %= label_for aliases =>'Aliases' 7 | %= text_area aliases => (cols => 63, rows => 3) 8 |
    9 | %= label_for site_name =>'Site_name' 10 | %= text_field site_name => (required => 1, size => 63) 11 |
    12 | %= label_for 'description' => 'Description' 13 |
    14 | %= text_area 'description' => (required => 1, size => 2000 , colls => 40, rows => 5) 15 |
    16 | %= label_for owner_id => 'Owner_id' 17 | %= number_field 'owner_id' 18 |
    19 | %= label_for group_id => 'Group_id' 20 | %= number_field 'group_id' 21 |
    22 | %= label_for permissions =>'Permissions' 23 | %= text_field permissions => (size => 10) 24 |
    25 | %= label_for published => 'Published' 26 | %= number_field published => (required => 1, size => 1) 27 |
    28 | <%= 29 | select_box templates => [['никаква' => '' ], @{config->{domove_templates}}] => 30 | (label => 'Теми', 'title' => 'Тема за този сайт. За да работи, папката с образците трябва да съществува.'); 31 | %> 32 |
    33 | %= submit_button $caption 34 | % end 35 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/domove/create.html.ep: -------------------------------------------------------------------------------- 1 | % my $title = 'New record in table "domove"'; 2 | % layout 'upravlenie', title => $title; 3 |

    <%= $title %>

    4 | %= include 'domove/_form', caption => 'Create', target => 'store_domove' 5 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/domove/edit.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Edit domove'; 2 |

    Edit domove

    3 | %= include 'domove/_form', caption => 'Update', target => 'update_domove' 4 | %= button_to Remove => remove_domove => {id => $id} 5 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/domove/index.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Blog/Domove'; 2 | % my @columns = qw(id domain site_name description owner_id group_id permissions published); #table columns 3 | %= link_to 'New record in table "domove"' => 'create_domove' 4 | 5 | 6 | 7 | % for my $column (@columns) { 8 | 9 | % } 10 | 11 | 12 | 13 | % for my $item (@$domove) { 14 | 15 | 16 | % for my $column (@columns[1 .. $#columns]) { 17 | 18 | % } 19 | 20 | % } 21 | 22 |
    <%= uc($column) %>
    <%= link_to $item->{id} => show_domove => {id => $item->{id}} %><%= $item->{$column} %>
    23 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/domove/show.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Record from table dom with id ' . $dom->{id}; 2 |

    <%= $dom->{id} . ':' . $dom->{'domain'} %>

    3 |

    <%= $dom->{'id'} %>

    4 |

    <%= $dom->{'domain'} %>

    5 |

    <%= $dom->{aliases} %>

    6 |

    <%= $dom->{'site_name'} %>

    7 |

    <%= $dom->{'description'} %>

    8 |

    <%= $dom->{'owner_id'} %>

    9 |

    <%= $dom->{'group_id'} %>

    10 |

    <%= $dom->{'permissions'} %>

    11 |

    <%= $dom->{'published'} %>

    12 |

    <%= $dom->{'templates'} %>

    13 | %= link_to 'Edit' => edit_domove => {id => $dom->{id}} 14 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/example/welcome.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie'; 2 | % title 'Welcome'; 3 |

    <%= $msg %>

    4 |

    5 | It is based on the Mojolicious real-time web framework. 6 | This page was generated from the template "templates/example/welcome.html.ep" 7 | and the layout "templates/layouts/default.html.ep", 8 | <%= link_to 'click here' => url_for %> to reload the page or 9 | <%= link_to 'here' => '/index.html' %> to move forward to a static page. 10 | % if (config 'perldoc') { 11 | To learn more, you can also browse through the documentation 12 | <%= link_to 'here' => '/perldoc' %>. 13 | % } 14 |

    15 |

    16 | % if($c->is_user_authenticated){ 17 | As authenticated user, <%= $c->user->{login_name} %>, 18 | you can go and <%= link_to manage => url_for('under_management')%> some resources. 19 | % } 20 | % else { 21 | You may want to <%=link_to 'sign in' => url_for('sign_in') %> to use all features of this application. 22 | % } 23 |

    24 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/groups/_form.html.ep: -------------------------------------------------------------------------------- 1 | %= form_for $target => begin 2 | 3 | %=hidden_field 'id' => $groups->{id} if ($action ne 'create'); 4 | %= label_for name =>'Name' 5 |
    6 | %= text_field name => $groups->{name}, required => 1, size => 100 7 |
    8 | %= label_for description =>'Description' 9 |
    10 | %= text_field description => $groups->{description}, required => 1, size => 255 11 |
    12 | %= label_for created_by => 'Created_by' 13 |
    14 | %= number_field created_by => $groups->{created_by}, 15 |
    16 | %= label_for changed_by => 'Changed_by' 17 |
    18 | %= number_field changed_by => $groups->{changed_by}, 19 |
    20 | %= label_for disabled => 'Disabled' 21 |
    22 | %= number_field disabled => $groups->{disabled}, required => 1, size => 1 23 |
    24 | 25 | %= submit_button $caption 26 | % end 27 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/groups/create.html.ep: -------------------------------------------------------------------------------- 1 | % my $title = 'New record in table "groups"'; 2 | % layout 'upravlenie', title => $title; 3 |

    <%= $title %>

    4 | %= include 'groups/_form', caption => 'Create', target => 'store_groups' 5 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/groups/edit.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Edit groups'; 2 |

    Edit groups

    3 | %= include 'groups/_form', caption => 'Update', target => 'update_groups' 4 | %= button_to Remove => remove_groups => {id => $groups->{id}} 5 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/groups/index.html.ep: -------------------------------------------------------------------------------- 1 | % my $title = 'manage/Множества'; 2 | % layout 'upravlenie'; title $title; 3 | 4 |

    <%== $title %>

    5 | % my @columns = qw(id name description created_by changed_by disabled); #table columns 6 | %= link_to 'New record in table "groups"' => 'create_groups' 7 | 8 | 9 | 10 | % for my $column (@columns) { 11 | 12 | % } 13 | 14 | 15 | 16 | % for my $item (@$groups) { 17 | 18 | 19 | % for my $column (@columns[1 .. $#columns]) { 20 | 21 | % } 22 | 23 | % } 24 | 25 |
    <%= uc($column) %>
    <%= link_to $item->{id} => show_groups => {id => $item->{id}} %><%= $item->{$column} %>
    26 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/groups/show.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => $groups->{title}; 2 |

    <%= $groups->{id} %>

    3 |

    <%= $groups->{'id'} %>

    4 |

    <%= $groups->{'name'} %>

    5 |

    <%= $groups->{'description'} %>

    6 |

    <%= $groups->{'created_by'} %>

    7 |

    <%= $groups->{'changed_by'} %>

    8 |

    <%= $groups->{'disabled'} %>

    9 | %= link_to 'Edit' => edit_groups => {id => $groups->{id}} 10 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/layouts/default.html.ep: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= title %> 6 | 7 | <%= content %> 8 | 9 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/layouts/podviewer.html.ep: -------------------------------------------------------------------------------- 1 | 2 | %= t html => begin 3 | 4 | 5 | 6 | 7 | 8 | 9 | %= t 'title' => title 10 | %= stylesheet '/css/slovo-min.css' 11 | %= stylesheet '/css/fonts.css' 12 | %= stylesheet '/css/site.css' 13 | %= stylesheet begin 14 | main, main section, article { 15 | font-family: sans-serif; 16 | } 17 | main::after {margin-bottom: 3rem;} 18 | % end 19 | %= javascript '/js/mui.min.js' 20 | %= javascript '/mojo/jquery/jquery.js' 21 | 22 | %= t body => (class => 'hide-sidedrawer') => begin 23 |
    24 | 25 | 26 | 29 | 30 |
    27 | %= title 28 |
    31 |
    32 | %= t main => (id => 'content-wrapper', class => 'mui-container-fluid') => begin 33 | %= content 34 | % end 35 | %= include 'partials/_footer' 36 | % end 37 | % end 38 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/layouts/upravlenie.html.ep: -------------------------------------------------------------------------------- 1 | 2 | %= t html => begin 3 | %= t head => begin 4 | %= t meta => (charset => 'utf-8') 5 | %= t 'title' => title 6 | %= stylesheet '/css/slovo-min.css' 7 | %= stylesheet '/css/upravlenie.css' 8 | %= stylesheet '/minion/fontawesome/fontawesome.css' 9 | %= javascript '/mojo/jquery/jquery.js' 10 | %= javascript '/js/mui.min.js' 11 | %= javascript '/js/trumbowyg-2.18/trumbowyg.min.js' 12 | %= javascript '/js/trumbowyg-2.18/plugins/base64/trumbowyg.base64.min.js' 13 | %= javascript '/js/trumbowyg-2.18/langs/bg.min.js' 14 | %= stylesheet '/js/trumbowyg-2.18/ui/trumbowyg.min.css' 15 | %= javascript '/js/editormd/editormd.min.js' 16 | %= javascript '/js/editormd/languages/en.js' 17 | %= stylesheet '/js/editormd/css/editormd.min.css' 18 | % end 19 | %= t body => begin 20 |
    21 | 22 | 23 | 30 | 36 | 37 |
    24 | %= link_to '/' => sub{ t img => id=> "logo", src=>"/img/slovo-white.png" } 25 | | 26 | %= link_to 'perldoc' => 'perldoc' =>(target => '_blank') 27 | | 28 | %= link_to '/manage' => 'under_management' 29 | 31 | % if ($c->is_user_authenticated) { 32 | % my $name = $c->user->{first_name} . ' ' . $c->user->{last_name}; 33 | %= link_to ' Изходъ '.$name => 'sign_out' 34 | % } 35 |
    38 |
    39 |
    40 | % my $messgage = flash('message'); 41 | %= $messgage ? t(div => (class => 'mui-panel field-with-error') => $messgage) : '' 42 | %= content 43 |
    44 | % end 45 | % end 46 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/partials/_stranici_index_pid-list.html.ep: -------------------------------------------------------------------------------- 1 | <% 2 | @$stranici || return; 3 | my $new_subpage_title = 'Нова подстраница'; 4 | my $new_celina = 'Ново писание'; 5 | my $list_page_content = 'Писания в страница'; 6 | my $param_pid = param('pid') // $stranici->[0]{pid}; 7 | my $time = time; 8 | %> 9 | 10 |
      11 | <% 12 | for my $p (@$stranici) { 13 | my $fa_class = 'fa-file'; 14 | my $podstranici = []; 15 | if ($p->{is_dir}) { 16 | if ((@$breadcrumb && $p->{id} == $breadcrumb->[0]{pid}) || ($p->{id} == $param_pid)) 17 | { 18 | $podstranici = stranici->all_for_edit($user, $domain->{domain}, $l, 19 | {pid => $p->{id}, columns => [@$stranici_columns], order_by => 'sorting'}); 20 | shift @$breadcrumb if @$breadcrumb; 21 | } 22 | $fa_class = @$podstranici ? 'fa-folder-open' : 'fa-folder'; 23 | } 24 | my $class = join ' ', map { $p->{$_} ? $_ : () } qw(hidden deleted); 25 | $class .= ($p->{stop} // 0) != 0 && ($p->{stop} // 0) < $time ? ' expired' : ''; 26 | $class .= ($p->{start} // 0) != 0 && ($p->{start} // 0) > $time ? ' upcoming' : ''; 27 | %> 28 |
    • 29 | 30 | <%= 31 | $p->{is_dir} 32 | ? link_to $p->{alias} => url_for->query(pid => $p->{id}), 33 | (class => $class . ($p->{id} == $param_pid ? ' mui--color-deep-orange' : 0)) 34 | : $p->{alias} 35 | %> 36 |
      37 | 41 |
        42 | % if ($c->is_item_editable($p)) { 43 |
      • 44 | <%= link_to 45 | url_for(edit_stranici => {id => $p->{id}}) 46 | ->query(language=>$p->{language}) => (title => 'Промѣна') => 47 | begin %> Промѣна<% end %> 48 |
      • 49 | % } 50 |
      • 51 | <%= 52 | link_to 53 | '🖺 Прегледъ' => 54 | url_for(page => {page => $p->{alias}})->query([прегледъ => 1]), 55 | (title => 'Прегледъ', target => '_blank') 56 | %> 57 |
      • 58 | 59 |
      • 60 | <%= 61 | link_to url_for(show_stranici => {id => $p->{id}}), 62 | (title => 'Свойства', target => '_blank')=> begin %> Свойства<% end %> 64 |
      • 65 | % if(!$p->{deleted} && !$p->{hidden}) { 66 |
      • 67 | <% if($p->{is_dir}){ %> 68 | <%= link_to url_with('create_stranici') 69 | ->query(pid => $p->{id}, dom_id => $p->{dom_id}), 70 | (title => "$new_subpage_title на „$p->{alias}”") => begin %> <%= $new_subpage_title %><% end %> 72 |
      • 73 | <% } %> 74 |
      • 75 | <%= link_to url_with('create_celini')->query(page_id => $p->{id}), 76 | (title => "$new_celina в „$p->{alias}”") => begin %> <%= $new_celina %><% end %> 79 |
      • 80 |
      • 81 | <%= link_to url_for(celini_in_stranica => page_id => $p->{id}), 82 | (title => "$list_page_content „$p->{alias}”") => begin %> <%= $list_page_content %><% end %> 84 |
      • 85 | % } 86 |
      87 |
      88 | <%= 89 | $podstranici ? include 'partials/_stranici_index_pid-list' => (stranici => $podstranici): '' 90 | %> 91 |
    • 92 | <% 93 | } 94 | %> 95 |
    96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/stranici/create.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Нова страница'; 2 | %= include 'stranici/_form', caption => 'Create', target => 'store_stranici' 3 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/stranici/edit.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => defined ${id} ? "Промяна на страница ${id}" : 'Нова страница'; 2 | %= include 'stranici/_form', caption => 'Update', target => 'update_stranici' 3 | %#= button_to Remove => remove_stranici => {id => $in->{id}}, (style =>'float:right') 4 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/stranici/index.html.ep: -------------------------------------------------------------------------------- 1 | <% 2 | layout 'upravlenie', title => 'Страници'; 3 | my $stranici = stranici->all_for_edit($user, $domain->{domain}, $l, 4 | {columns => [@$stranici_columns], order_by =>'sorting'}); 5 | %> 6 | 19 | 20 | 21 |
    22 | <%= 23 | include 'partials/_stranici_index_pid-list' => (stranici => $stranici) 24 | %> 25 |
    26 | 27 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/stranici/show.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Record from table stranici with id ' . $stranici->{id}; 2 |

    <%= $stranici->{id} %>

    3 | %= link_to '⮉ 🗐' => 'home_stranici' => (title => 'Up to list of stranici') 4 |
    5 | % for my $s (keys %$stranici) { 6 | <%== 7 | t span => (id => $s, class => 'item-field') => "$s: " . ($stranici->{$s} // '') 8 | %> 9 | % } 10 |
    11 | %= link_to 'Edit' => edit_stranici => {id => $stranici->{id}} 12 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/stranici/templates/README: -------------------------------------------------------------------------------- 1 | In this folder (and the same folder in the respective domain folder) you can 2 | put your own page templates. They will be shown in the dorpdown box of the form 3 | for creating pages in the administration area. This gives you full flexibility 4 | to choose your own layouts per page and make differently looking pages. 5 | 6 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/task/send_onboarding_email.txt.ep: -------------------------------------------------------------------------------- 1 | % use Time::Piece (); 2 | % my $t = Time::Piece->new($time); 3 | Здравейте, <%= $to_user->{first_name}.' '. $to_user->{last_name} %>. 4 | Днес, <%= $t->strftime('%F') %> в <%= $t->strftime('%H:%M:%S %Z') %>, <%= 5 | $from_user->{first_name}.' '. $from_user->{last_name} %> 6 | създаде потребителска сметка за вас в <%= $domain %>. 7 | 8 | Вашето потребителско име е „<%= $to_user->{login_name}%>“ без ограждащите кавички. 9 | 10 | Отидете на адрес http://<%= $domain %>/first_login/<%= $token %>, 11 | за да влезете за първи път и промените тайния си ключ за вход. 12 | 13 | Препратката по-горе ще работи до <%= 14 | Time::Piece->new(time + $token_valid_for)->strftime('%F %H:%M:%S %Z') %> или докато влезете. 15 | Благодарим ви, че помагате за осъществяването на това предприятие. 16 | 17 | С благодарност от създателите на <%= $domain %>: <%= $t->strftime('%F') %>. 18 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/task/send_passw_email.txt.ep: -------------------------------------------------------------------------------- 1 | % use Time::Piece (); 2 | % my $t = Time::Piece->new($time); 3 | Здравейте, <%= $to_user->{first_name}.' '. $to_user->{last_name} %>. 4 | Днес, <%= $t->strftime('%F') %> в <%= $t->strftime('%H:%M:%S %Z') %>, вие или някой, 5 | използвал вашата електронна поща <%= $to_user->{email} %> създадe временен таен ключ 6 | за влизане в <%= $domain %>. 7 | 8 | Вашето потребителско име е „<%= $to_user->{login_name}%>“ без ограждащите кавички. 9 | 10 | Отидете на адрес http://<%= $domain %>/in и използвайте този ключ: <%= $token %>, 11 | за да влезете и промените постоянния си таен ключ за вход. 12 | Ключът е в сила до <%= 13 | Time::Piece->new(time + $token_valid_for)->strftime('%F %H:%M:%S %Z') %> или докато влезете. 14 | 15 | С поздрав от създателите на <%= $domain %>: <%= $t->strftime('%F') %>. 16 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/upravlenie/index.html.ep: -------------------------------------------------------------------------------- 1 | <% 2 | my $title = 'manage'; 3 | layout 'upravlenie'; 4 | title $title; 5 | my $menu_links = []; 6 | my $root_page 7 | = $c->stranici->find_where({page_type => $page_types->[0], dom_id => $domain->{id}}); 8 | foreach my $el (@$menu) { 9 | if ($el eq 'celini') { 10 | push @$menu_links, 11 | link_to url_for(celini_in_stranica => page_id => $root_page->{id}) => 12 | sub {'Цѣлини'}; 13 | } 14 | else { 15 | push @$menu_links, link_to $el => "home_$el"; 16 | } 17 | } 18 | %> 19 |

    <%== $title %>

    20 | 23 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/users/_form.html.ep: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | %= form_for $target => (class => 'mui-form') => begin 4 |
    5 | % my $name_title = 'Полето „Име за вход“ може да съдържа от 4 до 12 букви, цифри и знаците „.“,„-“ и „$“'; 6 | %= text_field 'login_name', placeholder => $name_title,title=> $name_title 7 | %= label_for login_name =>'Име за вход', title => $name_title 8 |
    9 | % my $l_p_title = 'Създайте случаен таен ключ, като използвате бутона (+).'; 10 |
    11 | 14 | <%= 15 | text_field login_password => ( 16 | readonly => '', 17 | required => '', 18 | size => 40, 19 | title => $l_p_title, 20 | placeholder => $l_p_title, 21 | style =>'width:92%' 22 | ) 23 | %> 24 | %= label_for login_password =>'Таен ключ за вход', title => $l_p_title 25 |
    26 |
    27 | %= text_field first_name => required => 1, size => 40 28 | %= label_for first_name =>'Име' 29 |
    30 |
    31 | %= text_field last_name => required => 1, size => 40 32 | %= label_for last_name =>'Фамилия' 33 |
    34 |
    35 | %= text_field email => required => 1, size => 40 36 | %= label_for email =>'Е-поща' 37 |
    38 |
    39 | %= text_field description =>size => 65, class => 'mui--is-empty mui--is-untouched mui--is-pristine' 40 | %= label_for description =>'Описание' 41 |
    42 | %= select_box disabled => [['Не'=>1],['Да'=>0]], label =>'Действащ' 43 |
    44 | %= number_field 'start_date' 45 | %= label_for start_date => 'Start_date' 46 |
    47 |
    48 | %= number_field 'stop_date' 49 | %= label_for stop_date => 'Stop_date' 50 |
    51 | % if($target eq 'update_users' && stash->{id}) { 52 | <% 53 | my $groups = groups->all_with_member(stash->{id}); 54 | my $is_admin = !!groups->is_admin($user->{id}); 55 | $groups = $groups->map(sub { 56 | return [ 57 | $_->{name} => $_->{id}, 58 | $_->{is_member} ? (checked => undef) : (), 59 | !$is_admin || ($_->{id} == $user->{group_id}) ? (disabled => '') : ()]; 60 | }); 61 | %> 62 | <%= checkboxes(groups => $groups, label =>'Множества') %> 63 |
    64 | %= label_for group_id => 'Главно множество' 65 | <%= groups->find_where({id => $users->{group_id}})->{name} %> 66 | %= hidden_field group_id => $users->{group_id} 67 |
    68 | 69 | % } 70 | <%= input_tag cancel => 'Отказъ', 71 | (type=>'reset', class => 'mui-btn mui-btn--secondary', style => 'float :right;') %> 72 | % my $button_txt = $caption eq 'Create' ? 'Създаване':'Записъ'; 73 | %= submit_button $button_txt => (class => 'mui-btn mui-btn--raised mui-btn--primary') 74 | % end 75 | 81 | %= javascript '/js/CryptoJS-v3.1.2/sha1.js' 82 | %= javascript begin 83 | "use strict"; 84 | function generate_password(e) { 85 | e.preventDefault(); 86 | const name_field = $('[name="login_name"]'); 87 | const passw_field = $('[name="login_password"]'); 88 | if (name_field.val() === "") { 89 | alert( 90 | "Моля, въведете име за потребителя," + 91 | " преди да създадете тайния ключ за вход" + 92 | " и не го променяйте след това." 93 | ); 94 | return false; 95 | } 96 | let length = 8; 97 | let charset = 98 | "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯ" + 99 | "абвгдежзийклмнопрстуфхцчшщъьюя" + 100 | "1234567890!@#$%^&*()_+-=[];:,.~/<>"; 101 | let pass = ""; 102 | for (let i = 0, n = charset.length; i < length; ++i) { 103 | pass += charset.charAt(Math.floor(Math.random() * n)); 104 | } 105 | if (same_user) { 106 | pass = prompt( 107 | `Новият таен ключ за вход е "${pass}" без ограждащите кавички. ` + 108 | "Състои се от букви от българската азбука, числа и печатаеми знаци. " + 109 | "Можете да го промените, но го запомнете, защото няма да го видите повече.", 110 | pass 111 | ); 112 | if (Boolean(pass)) passw_field.val(pass); 113 | else return; 114 | } else { 115 | let change_pass = 116 | form_target === "update_users" 117 | ? confirm( 118 | "Наистина ли искате да промените тайната дума за вход на " + 119 | $('[name="login_name"]').val() + 120 | "?" 121 | ) 122 | : true; 123 | if (Boolean(change_pass)) passw_field.val(pass); 124 | else return; 125 | } 126 | const concat_ln_lp = name_field.val() + passw_field.val(); 127 | const passw_sha1 = CryptoJS.SHA1(concat_ln_lp); 128 | passw_field.val(passw_sha1); 129 | return; 130 | } 131 | 132 | $("#generate_pass").click(generate_password); 133 | % end 134 |
    135 |
    136 | 137 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/users/create.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Нова потрѣбителска сметка'; 2 |

    <%= title %>

    3 | %= link_to '☰ Потрѣбители' => 'home_users' 4 | %= include 'users/_form', caption => 'Create', target => 'store_users' 5 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/users/edit.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Промѣна на потрѣбителска сметка'; 2 |

    <%= title %>

    3 | %= button_to Remove => remove_users => {id => $users->{id}} =>(style=>'float:right') 4 | %= link_to '☰ Потрѣбители' => 'home_users' 5 | %= include 'users/_form', caption => 'Update', target => 'update_users' 6 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/users/index.html.ep: -------------------------------------------------------------------------------- 1 | % my $title = 'manage/Потребители'; 2 | % layout 'upravlenie'; title $title; 3 |

    <%== $title %>

    4 | 5 | % my @columns = qw(login_name first_name last_name start_date stop_date); #table columns 6 | %= link_to 'New record in table "users"' => 'create_users' 7 | 8 | 9 | 10 | 11 | % for my $column (@columns) { 12 | 13 | % } 14 | 15 | 16 | 17 | <% 18 | for my $item (@$users) { 19 | my $class = ($user->{id} == $item->{id} ? 'b' : '') 20 | . ($item->{disabled} ? ' disabled' : ''); 21 | %> 22 | > 23 | 24 | % for my $column (@columns) { 25 | 26 | % } 27 | 28 | % } 29 | 30 |
    ID<%= uc($column) %>
    <%= link_to $item->{id} => show_users => {id => $item->{id}} %><%= $item->{$column} %>
    31 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/users/show.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Прѣгледъ на потребителска сметка на '. $users->{login_name}; 2 |

    <%= title %>

    3 | %= link_to '☰ Потрѣбители' => 'home_users' 4 | | 5 | %= link_to '📝 Промѣна' => edit_users => {id => $users->{id}} 6 | 7 |
    8 |
    9 | % for my $k (keys %$users) { 10 | <%== t span => (id =>$k, class => 'item-field') 11 | => "$k: " . ($users->{$k} // '') %> 12 | % } 13 |
    14 |
    15 | 16 | -------------------------------------------------------------------------------- /lib/Slovo/resources/templates/users/store_result.html.ep: -------------------------------------------------------------------------------- 1 | % layout 'upravlenie', title => 'Създаване на потребителска сметка'; 2 | % my $result =$job->info->{result}; 3 |

    Създаване на потребителска сметка

    4 |
    5 |
    6 | % unless($result) { 7 | %# TODO: I18n & L10n 8 | % my $from_user = $job->args->[0]; 9 | % my $to_user = $job->args->[1]; 10 |

    11 | Скоро ще бъде изпратено електронно писмо от 12 | <%= $from_user->{first_name}. ' ' .$from_user->{last_name} %> 13 | до пощенската кутия на току-що създадената 14 | сметка за новия потребител 15 | <%= $to_user->{first_name}. ' ' .$to_user->{last_name} %>. 16 | За да видите дали писмото е изпратено, моля 17 | презаредете страницата. (Ctrl+R или натиснете бутона за презареждане.) 18 |

    19 |

    20 | В писмото новият потребител ще намери връзка с таен адрес за вход, който 21 | трябва да посети. 22 |

    23 |

    24 | Чрез този адрес потребителят ще влезе за първи път в системата на слово.бг 25 | и ще може да създаде за себе си нов таен ключ за вход. На входа за 26 | допълнителна сигурност ще бъде попитан за вашите имена, собствено и 27 | фамилно, така както сте ги въвели в слово.бг. В самото писмо ще бъдат 28 | дадени указания, така че вие не трябва да му обяснявате всичко това. 29 |

    30 |

    31 | В случай че първото влизане на новия потребител се провали, може да се 32 | наложи да промените стойността на полето „Действащ“ в сметката му. 33 | Стойността по подразбиране е „Не“ и се сменя на „Да“, когато потребителят 34 | влезе успешно за първи път. Ако промените тази стойност на „Да“, 35 | потребителят ще може да използва възможността за влизане с временен таен 36 | ключ, въпреки, че не е успял да задейства сам сметката чрез първото си 37 | влизане. 38 |

    39 |

    Благодарим за подкрепата.

    40 | % } else { 41 |

    <%= $result %>

    42 | %} 43 |
    44 |
    45 | 46 | -------------------------------------------------------------------------------- /script/slovo: -------------------------------------------------------------------------------- 1 | #!perl 2 | use strict; 3 | use warnings; 4 | use FindBin qw($Bin); 5 | use Cwd qw(abs_path); 6 | use lib (); 7 | 8 | BEGIN { 9 | for ("$Bin/../local/lib/perl5", "$Bin/../lib/perl5", "$Bin/../lib", "$Bin/../site/lib") 10 | { 11 | lib->import(abs_path $_) if (-d $_); 12 | } 13 | } 14 | 15 | use Mojolicious::Commands; 16 | 17 | # Start command line interface for application 18 | Mojolicious::Commands->start_app('Slovo'); 19 | 20 | -------------------------------------------------------------------------------- /script/slovo.service: -------------------------------------------------------------------------------- 1 | # This file assumes that you installed Slovo in /home/$USER/opt/slovo 2 | # like this: 3 | # cpanm -L ~/opt/slovo Slovo 4 | # Modify this file by replacing $USER with your username and copy it to 5 | # /etc/systemd/system/slovo.service 6 | # For details see https://askubuntu.com/questions/676007 7 | # and then for more https://wiki.ubuntu.com/SystemdForUpstartUsers 8 | 9 | [Unit] 10 | Description=Slovo - Искони бѣ Слово 11 | Requires=network.target 12 | After=network.target 13 | # put here other service requirements 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | 18 | [Service] 19 | WorkingDirectory=/home/$USER/opt/slovo/bin 20 | Restart=always 21 | Type=forking 22 | User=$USER 23 | Group=$USER 24 | SyslogIdentifier=slovo 25 | PIDFile=/home/$USER/opt/slovo/bin/slovo.pid 26 | ExecStart=/home/$USER/opt/slovo/bin/hypnotoad slovo 27 | ExecStop=/home/$USER/opt/slovo/bin/hypnotoad -s slovo 28 | ExecReload=/home/$USER/opt/slovo/bin/hypnotoad slovo 29 | 30 | -------------------------------------------------------------------------------- /script/slovo_minion.service: -------------------------------------------------------------------------------- 1 | # This file assumes that you installed Slovo in /home/$USER/opt/slovo 2 | # like this: 3 | # cpanm -L ~/opt/slovo Slovo 4 | # Modify this file by replacing $USER with your username and copy it to 5 | # /etc/systemd/system/slovo.service 6 | # For details see https://askubuntu.com/questions/676007 7 | # and then for more https://wiki.ubuntu.com/SystemdForUpstartUsers 8 | 9 | [Unit] 10 | Description=Роботници за Слово 11 | After=slovo.service 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | 16 | [Service] 17 | Type=simple 18 | ExecStart=/home/$USER/opt/slovo/bin/slovo minion worker -m production 19 | KillMode=process 20 | WorkingDirectory=/home/$USER/opt/slovo/bin 21 | User=$USER 22 | Group=$USER 23 | SyslogIdentifier=slovo_minion 24 | -------------------------------------------------------------------------------- /t/001_load.t: -------------------------------------------------------------------------------- 1 | # -*- perl -*- 2 | 3 | # t/001_load.t - check module loading 4 | use Test::More tests => 1; 5 | use_ok('Slovo'); 6 | 7 | 8 | -------------------------------------------------------------------------------- /t/002_basic.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use Test::Mojo; 4 | my $t = Test::Mojo->with_roles('+Slovo')->install()->new('Slovo'); 5 | $t->get_ok('/')->status_is(200)->content_like(qr/Слово/i); 6 | 7 | done_testing(); 8 | -------------------------------------------------------------------------------- /t/002_cache.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | 3 | use Test::More; 4 | use Slovo::Cache; 5 | 6 | subtest 'Basics' => sub { 7 | my $cache = Slovo::Cache->new(max_keys => 2); 8 | is $cache->get('foo'), undef, 'no result'; 9 | $cache->set(foo => 'bar'); 10 | is $cache->get('foo'), 'bar', 'right result'; 11 | $cache->set(bar => 'baz'); 12 | is $cache->get('foo'), 'bar', 'right result'; 13 | is $cache->get('bar'), 'baz', 'right result'; 14 | $cache->set(baz => 'yada'); 15 | is $cache->get('foo'), undef, 'no result'; 16 | is $cache->get('bar'), 'baz', 'right result'; 17 | is $cache->get('baz'), 'yada', 'right result'; 18 | $cache->set(yada => 23); 19 | is $cache->get('foo'), undef, 'no result'; 20 | is $cache->get('bar'), undef, 'no result'; 21 | is $cache->get('baz'), 'yada', 'right result'; 22 | is $cache->get('yada'), 23, 'right result'; 23 | $cache->max_keys(1)->set(one => 1)->set(two => 2); 24 | is $cache->get('one'), undef, 'no result'; 25 | is $cache->get('two'), 2, 'right result'; 26 | }; 27 | 28 | subtest 'Bigger cache' => sub { 29 | my $cache = Slovo::Cache->new(max_keys => 3); 30 | is $cache->get('foo'), undef, 'no result'; 31 | is $cache->set(foo => 'bar')->get('foo'), 'bar', 'right result'; 32 | $cache->set(bar => 'baz'); 33 | is $cache->get('foo'), 'bar', 'right result'; 34 | is $cache->get('bar'), 'baz', 'right result'; 35 | $cache->set(baz => 'yada'); 36 | is $cache->get('foo'), 'bar', 'right result'; 37 | is $cache->get('bar'), 'baz', 'right result'; 38 | is $cache->get('baz'), 'yada', 'right result'; 39 | $cache->set(yada => 23); 40 | is $cache->get('foo'), undef, 'no result'; 41 | is $cache->get('bar'), 'baz', 'right result'; 42 | is $cache->get('baz'), 'yada', 'right result'; 43 | is $cache->get('yada'), 23, 'right result'; 44 | }; 45 | 46 | subtest 'Cache disabled' => sub { 47 | my $cache = Slovo::Cache->new(max_keys => 0); 48 | is $cache->get('foo'), undef, 'no result'; 49 | is $cache->set(foo => 'bar')->get('foo'), undef, 'no result'; 50 | 51 | $cache = Slovo::Cache->new(max_keys => -1); 52 | is $cache->get('foo'), undef, 'no result'; 53 | $cache->set(foo => 'bar'); 54 | is $cache->get('foo'), undef, 'no result'; 55 | }; 56 | 57 | subtest 'Key prefix' => sub { 58 | my $templates = { 59 | '/partials/_foo.html.ep' => bless({foo => 1}, __PACKAGE__), 60 | 'bar.html.ep' => bless({foo => 2}, __PACKAGE__), 61 | }; 62 | 63 | my $c1 = Slovo::Cache->new(); 64 | isa_ok($c1 => 'Slovo::Cache'); 65 | isa_ok($c1 => 'Mojo::Cache'); 66 | is($c1->max_keys => 111 => 'default max keys'); 67 | is($c1->key_prefix => '' => 'default key_prefix'); 68 | for (keys %$templates) { 69 | $c1->set($_ => $templates->{$_}); 70 | ok($c1->get($_) => $_ . ' without prefix'); 71 | } 72 | 73 | my $c2 = Slovo::Cache->new(key_prefix => '-'); 74 | for (keys %$templates) { 75 | $c2->set($_ => bless({foo => 3}, __PACKAGE__)); 76 | ok($c2->get($_) => $_ . ' with prefix'); 77 | } 78 | 79 | # Keys with different prefix in the same object 80 | $c2->key_prefix('a'); 81 | 82 | for (keys %$templates) { 83 | $c2->set($_ => bless({foo => 4}, __PACKAGE__)); 84 | ok($c2->get($_) => $_ . ' with new prefix'); 85 | } 86 | is(keys %{$c2->cache} => 4, 'more keys'); 87 | }; 88 | 89 | done_testing(); 90 | -------------------------------------------------------------------------------- /t/002_database.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use FindBin qw($Bin); 3 | use Test::More; 4 | use Test::Mojo; 5 | use Mojo::File qw(path); 6 | 7 | # $ENV{MOJO_MIGRATIONS_DEBUG} = 1; 8 | my $t = Test::Mojo->with_roles('+Slovo')->install( 9 | 10 | # from => to 11 | # "$Bin/.." => '/home/berov/opt/t.com/slovo', 12 | # 0777 13 | )->new('Slovo'); 14 | my $app = $t->app; 15 | my $moniker = $app->moniker; 16 | my $mode = $app->mode; 17 | my $home = $app->home; 18 | my $dbx = $app->dbx; 19 | my $db = $dbx->db; 20 | 21 | 22 | my $triggers = sub { 23 | 24 | # Try to move a page under itself (ѿносно) 25 | my $table = $app->stranici->table; 26 | my $str = $db->select($table => '*' => {dom_id => 0, alias => 'ѿносно'})->hash; 27 | eval { $db->update($table, {pid => $str->{id}}, {id => $str->{id}}); }; 28 | 29 | # note $@; 30 | like $@ => qr/execute failed: s.pid cannot be equal to s.id/, 31 | 'pid cannot be equal to id'; 32 | 33 | # Create a regular page which is not a directory and move 'ѿносно' under it 34 | my $new_page_id = $app->stranici->add({ 35 | alias => 'нова', 36 | body => 'някакъв текст', 37 | changed_by => 5, 38 | data_format => 'text', 39 | group_id => 5, 40 | language => 'bg', 41 | page_type => 'regular', 42 | permissions => '-rwxrwxr-x', 43 | published => 0, 44 | title => 'нова', 45 | tstamp => time, 46 | user_id => 5, 47 | }); 48 | 49 | eval { $db->update($table, {pid => $str->{id}}, {id => $new_page_id}); }; 50 | 51 | # note $@; 52 | like $@ => qr/failed: The parent page must be a directory/, 53 | 'the parent page must be a directory'; 54 | 55 | # Celini писания 56 | # Try to move a celina under itself (ѿносно) 57 | my $ctable = $app->celini->table; 58 | my $cel = $db->select($ctable => '*' => {id => 1, alias => 'писания'})->hash; 59 | eval { $db->update($ctable, {pid => $cel->{id}}, {id => $cel->{id}}); }; 60 | 61 | # note $@; 62 | like $@ => qr/execute failed: c.pid cannot be equal to c.id/, 63 | 'pid cannot be equal to id'; 64 | 65 | # целина Ѿносно в целина Писания 66 | # 1|0|писания|-rwxr-xr-x|Писания 67 | # 4|0|ѿносно|-rwxr-xr-x|Ѿносно 68 | $app->celini->save(1, {permissions => '-rwxr-xr-x'}); 69 | eval { $db->update($ctable, {pid => 1}, {id => 4}); }; 70 | 71 | note $@; 72 | like $@ => qr/failed: The parent celina must be a directory/, 73 | 'the parent celina must be a directory'; 74 | }; 75 | 76 | subtest Triggers => $triggers; 77 | 78 | done_testing; 79 | -------------------------------------------------------------------------------- /t/003_sign_in.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use FindBin; 3 | use Test::More; 4 | use Test::Mojo; 5 | 6 | my $t = Test::Mojo->with_roles('+Slovo')->install( 7 | 8 | # undef, '/tmp/slovo_sign_in' 9 | )->new('Slovo'); 10 | isa_ok($t->app, 'Slovo'); 11 | 12 | $t->login_ok('краси', 'беров'); 13 | 14 | # TODO: Depending on the user and to where he headed redirect him after login 15 | # to eventually ->text_is('head title' => 'manage'); 16 | 17 | done_testing; 18 | -------------------------------------------------------------------------------- /t/006_api.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use Test::Mojo; 4 | use Mojo::ByteStream 'b'; 5 | my $t = Test::Mojo->with_roles('+Slovo')->install( 6 | 7 | # '.', '/tmp/slovo' 8 | )->new('Slovo'); 9 | my $app = $t->app; 10 | 11 | subtest 'api/stranici' => sub { 12 | $t->get_ok("/api/stranici")->status_is(200)->json_is('/0/alias' => 'писания'); 13 | 14 | #create several pages and then check the list again 15 | ok($t->login('краси', 'беров') => 'login ok'); 16 | my $sform = { 17 | page_type => 'regular', 18 | permissions => 'drwxr-xr-x', 19 | published => 2, 20 | title => 'Събития', 21 | body => 'Някaкъв по-дълъг теѯт, който е тяло на писанѥто.', 22 | language => 'bg-bg', 23 | data_format => 'text', 24 | pid => 0, 25 | }; 26 | my $stranici_url = $app->url_for('store_stranici')->to_string; 27 | my $pid = 9; 28 | my $stranici_url_new = $app->url_for('edit_stranici', id => $pid)->to_string; 29 | $t->post_ok($stranici_url => form => $sform)->status_is(302); 30 | $t->get_ok("/api/stranici")->status_is(200)->json_is('/2/alias' => 'събития'); 31 | $t->get_ok("/api/stranici?columns=id,alias,title")->json_is('/2/id' => $pid); 32 | 33 | # See lib/Slovo/resources/api-v1.0.json StraniciItem.required 34 | $t->get_ok("/api/stranici?columns=id,alias")->status_is(500); 35 | $t->json_is('/errors/2' => {message => 'Missing property.', path => '/body/2/title'}); 36 | 37 | # note explain $t->tx->res->json; 38 | $t->get_ok("/api/stranici?columns=id,alias,title")->status_is(200) 39 | ->json_is('/2' => {id => $pid, alias => 'събития', title => 'Събития'}); 40 | 41 | 42 | @$sform{qw(permissions pid)} = ('-rwxr-xr-x', $pid); 43 | my $id = $pid; 44 | 45 | for my $title (qw(Foo Bar Baz)) { 46 | $sform->{title} = $title; 47 | $t->ua->post($stranici_url => form => $sform); 48 | $id++; 49 | $t->get_ok("$stranici_url/$id/edit?language=bg-bg")->status_is(200) 50 | ->content_like(qr/$title/); 51 | } 52 | $t->ua->get('/out'); # logout 53 | $t->get_ok("/api/stranici?pid=$pid")->status_is(200)->json_is('/0/alias' => 'foo') 54 | ->json_is('/2/alias' => 'baz'); 55 | }; 56 | 57 | done_testing; 58 | -------------------------------------------------------------------------------- /t/007_domove.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use Test::Mojo; 4 | use Mojo::ByteStream 'b'; 5 | use Mojo::Collection 'c'; 6 | my $test_class = Test::Mojo->with_roles('+Slovo'); 7 | 8 | unless ($ENV{TEST_DOMAIN}) { 9 | plan( 10 | skip_all => qq|Advanced test. Do the following to run this test. 11 | \$ export TEST_DOMAIN="${\ $test_class->domain_aliases}" 12 | \$ sudo vim /etc/hosts and add the following domains to 127.0.0.1 13 | bg.localhost en.localhost ${\ $test_class->domain_aliases} 14 | Then run this test again. 15 | | 16 | ); 17 | } 18 | 19 | my $t = $test_class->install( 20 | 21 | # '.', '/tmp/slovo' 22 | )->new('Slovo'); 23 | 24 | my $app = $t->app; 25 | 26 | #make краси admin: 27 | $t->app->dbx->db->insert("user_group" => {user_id => 5, group_id => 1}); 28 | 29 | subtest create_domain_and_page => sub { 30 | my ($dom_id) = $t->create_edit_domain_ok() =~ /(\d+)$/; 31 | my $form = { 32 | alias => 'коренъ', 33 | title => 'Добре дошли!', 34 | page_type => 'коренъ', 35 | body => 'Добре сте ни дошли на този сайт.', 36 | language => 'bg-bg', 37 | published => 2, 38 | permissions => '-rwxr-xr-x', 39 | dom_id => $dom_id, 40 | data_format => 'text', 41 | }; 42 | 43 | # TODO: add negative test. Implement validation check against adding 44 | # anodher root page in the same domain. 45 | $t->post_ok($app->url_for('store_stranici') => form => $form)->status_is(302); 46 | }; 47 | 48 | subtest visit_domains => sub { 49 | 50 | #logout 51 | $t->get_ok($app->url_for('sign_out'))->status_is(302); 52 | 53 | # visit first two domains' root pages 54 | my $domove = $app->domove->all({limit => 2}); 55 | $domove->each(sub { 56 | my $d = shift; 57 | my @aliases = $d->{domain}; 58 | push @aliases, split /\s+/, $d->{aliases}; 59 | my $page = $app->dbx->db->select( 60 | ['stranici', 'celini'], 61 | ['title', 'body'], 62 | { 63 | 'dom_id' => $d->{id}, 64 | 'data_type' => 'title', 65 | 'celini.page_id' => {-ident => 'stranici.id'}})->hashes->[0]; 66 | for my $alias (@{c(@aliases)->uniq}) { 67 | my $url = $t->ua->server->nb_url->host($alias); 68 | $t->get_ok($url)->status_is(200)->text_is('head > title' => $page->{title}) 69 | ->text_like('body section.title' => qr/$page->{body}/); 70 | } 71 | }); 72 | }; 73 | 74 | done_testing(); 75 | 76 | -------------------------------------------------------------------------------- /t/008_first_login.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use Test::Mojo; 4 | use Mojo::ByteStream 'b'; 5 | use Mojo::File qw(path); 6 | use Mojo::Util qw(encode sha1_sum url_escape); 7 | my $t = Test::Mojo->with_roles('+Slovo')->install( 8 | 9 | # '.' => '/tmp/slovo' 10 | )->new('Slovo'); 11 | my $app = $t->app; 12 | $t->login_ok(); 13 | my $user_form = {}; 14 | 15 | #create anew user and simulate addition of token for first login 16 | my $users_url = $app->url_for('home_users')->to_string; 17 | my ($token_row, $user); 18 | my $create_user = sub { 19 | $user_form = { 20 | login_name => 'шестi', 21 | login_password => sha1_sum(encode('utf8', "шестilabala")), 22 | first_name => 'Шести', 23 | last_name => 'Шестак', 24 | email => 'шести@хост.бг', 25 | disabled => 1, 26 | }; 27 | $t->post_ok($users_url => form => $user_form)->status_is(302); 28 | $user = $app->users->find_by_login_name('шестi'); 29 | ok(!defined $user, 'disabled user is not findable'); 30 | $user = $app->users->find_where({login_name => 'шестi'}); 31 | is($user->{disabled} => 1, 'new disabled user created'); 32 | $app->minion->perform_jobs; 33 | $token_row = $app->dbx->db->select('first_login', '*', 34 | {from_uid => $user->{created_by}, to_uid => $user->{id}})->hash; 35 | ok($token_row => 'token for first login creataed by job'); 36 | my $jobs = $app->dbx->db->select('minion_jobs', '*')->hashes; 37 | ok($jobs->[0]->{finished} => 'first job is finished'); 38 | ok(!$jobs->[1]->{finished}, 'second job is not finished'); 39 | }; 40 | 41 | my $first_login = sub { 42 | $t->get_ok('/first_login/' . $token_row->{token})->status_is(200) 43 | ->element_exists('[name="first_name"]')->element_exists('[name="last_name"]'); 44 | my $from_u = $app->users->find($user->{created_by}); 45 | $t->post_ok( 46 | '/first_login/', 47 | form => { 48 | first_name => $from_u->{first_name}, 49 | last_name => $from_u->{last_name}, 50 | token => $token_row->{token}}); 51 | $t->status_is(302) 52 | ->header_is(Location => $app->url_for('edit_users' => {id => $user->{id}})); 53 | $app->minion->perform_jobs; 54 | my $jobs = $app->dbx->db->select('minion_jobs', '*')->hashes; 55 | ok($jobs->[2]->{finished} => 'third job is finished'); 56 | ok(!$jobs->[1]->{finished}, 'second job is not finished'); 57 | ok( 58 | !defined $app->dbx->db->select('first_login', '*', {token => $token_row->{token}}) 59 | ->hash, 60 | 'delete_first_login successful' 61 | ); 62 | }; 63 | my $passw_login = sub { 64 | 65 | $t->get_ok($app->url_for('sign_out'))->status_is(302); 66 | $t->post_ok('/in' => {}, form => {login_name => 'шестi', login_password => 'грешѧ'}) 67 | ->element_exists('#passw_login'); 68 | $t->get_ok('/lost_password')->status_is(200)->element_exists('[name="email"]'); 69 | $t->post_ok('/lost_password' => {}, form => {email => $user_form->{email}}) 70 | ->status_is(200); 71 | $app->minion->perform_jobs; 72 | my $jobs = $app->dbx->db->select('minion_jobs', '*')->hashes; 73 | $user = $app->users->find_by_login_name('шестi'); 74 | is($jobs->[3]->{state} => 'finished', 'mail_passw_login job is finished'); 75 | is($jobs->[4]->{state} => 'inactive', 'delete_passw_login job is not finished'); 76 | $token_row = $app->dbx->db->select('passw_login', '*', {to_uid => $user->{id}})->hash; 77 | is($user->{disabled} => 0, 'user is enabled'); 78 | ok($t->login($user->{login_name}, $token_row->{token}) => 'passw_login ok'); 79 | my $eu_url = $app->url_for('edit_users' => {id => $user->{id}}); 80 | $t->header_is(Location => $eu_url); 81 | $t->get_ok($eu_url)->element_exists('.mui-panel.field-with-error'); 82 | }; 83 | subtest create_user => $create_user; 84 | subtest first_login => $first_login; 85 | subtest passw_login => $passw_login; 86 | done_testing; 87 | -------------------------------------------------------------------------------- /t/009_cgi_script.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use Test::Mojo; 4 | use Mojo::File qw(path); 5 | use Mojo::Util qw(decode); 6 | my $t = Test::Mojo->with_roles('+Slovo')->install( 7 | 8 | # from => to 9 | # "." => '/home/berov/opt/t.com/slovo', 10 | # 0777 11 | )->new('Slovo'); 12 | my $app = $t->app; 13 | my $moniker = $app->moniker; 14 | my $mode = $app->mode; 15 | my $home = $app->home; 16 | my $COMMAND = 'Slovo::Command::Author::generate::cgi_script'; 17 | require_ok($COMMAND); 18 | my $command = $COMMAND->new(app => $app); 19 | isa_ok($command => 'Slovo::Command'); 20 | 21 | # Default values 22 | my $buffer = ''; 23 | { 24 | open my $handle, '>', \$buffer; 25 | local *STDOUT = $handle; 26 | $command->run; 27 | } 28 | like $buffer => qr/Assuming\sscript\sname:\s$moniker\.cgi/x => 'default cgi script name'; 29 | like $buffer => qr/Assuming\smode:\s$mode/x => 'default mode'; 30 | like $buffer => qr/write.+$home\/$moniker\.cgi/x => "creating $moniker.cgi"; 31 | my $cgi = path "$home/$moniker.cgi"; 32 | ok -f $cgi => "created $cgi"; 33 | my $exe = $command->exe; 34 | note '$exe: ', $exe; 35 | ok((-f $exe) => 'exe exists'); 36 | my $cgi_content = $cgi->slurp; 37 | like $cgi_content => qr/\s'$mode';/ => "cgi sets current mode as default"; 38 | like $cgi_content => qr/\s'$home';/ => "cgi sets current home as default"; 39 | 40 | # Passed values 41 | $buffer = ''; 42 | { 43 | 44 | $mode = 'production'; 45 | Slovo->new(mode => $mode)->dbx->migrations->migrate; 46 | open my $handle, '>', \$buffer; 47 | local *STDOUT = $handle; 48 | $command->run('-c' => $mode, '--filename' => 'index.cgi'); 49 | } 50 | 51 | unlike $buffer => qr/Assuming/x => 'passed cgi script name as argument'; 52 | unlike $buffer => qr/Assuming/x => 'passed mode as argument'; 53 | $cgi = path "$home/index.cgi"; 54 | like $buffer => qr/write.+$cgi/x => "creating cgi"; 55 | ok(-f $cgi => "created $cgi"); 56 | $exe = $command->exe; 57 | note $exe; 58 | ok(-f $exe => 'exe exists'); 59 | $cgi_content = $cgi->slurp; 60 | like $cgi_content => qr/\s'$mode';/ => "cgi sets passed mode as default"; 61 | like $cgi_content => qr/\s'$home';/ => "cgi sets current home as default"; 62 | 63 | #cgi executes properly 64 | { 65 | local $ENV{MOJO_MODE}; 66 | local $ENV{MOJO_HOME}; 67 | my $out = qx/$cgi cgi 2>&1/; 68 | like decode('UTF-8' => $out) => qr/Добре дошли/ => "cgi output" 69 | } 70 | 71 | done_testing; 72 | 73 | -------------------------------------------------------------------------------- /t/010_a2htaccess.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use FindBin qw($Bin); 3 | use Test::More; 4 | use Test::Mojo; 5 | use Mojo::File qw(path); 6 | use Mojo::Util qw(decode); 7 | my $t = Test::Mojo->with_roles('+Slovo')->install( 8 | 9 | # from => to 10 | # "$Bin/.." => '/home/berov/opt/t.com/slovo', 11 | # 0777 12 | )->new('Slovo'); 13 | my $app = $t->app; 14 | my $moniker = $app->moniker; 15 | my $mode = $app->mode; 16 | my $home = $app->home; 17 | unlink $home->dirname . '/.htaccess'; 18 | my $COMMAND = 'Slovo::Command::Author::generate::a2htaccess'; 19 | require_ok($COMMAND); 20 | my $command = $COMMAND->new(app => $app); 21 | isa_ok($command => 'Slovo::Command'); 22 | 23 | # Default values 24 | my $buffer = ''; 25 | { 26 | open my $handle, '>', \$buffer; 27 | local *STDOUT = $handle; 28 | require Slovo::Command::Author::generate::cgi_script; 29 | Slovo::Command::Author::generate::cgi_script->new(app => $app)->run; 30 | $command->run; 31 | } 32 | note '$home:' . $home; 33 | note $buffer; 34 | my $docroot = $home->dirname; 35 | like $buffer => qr/Assuming\sDocumentRoot:\s$docroot/x => 'default DocumentRoot'; 36 | like $buffer => qr/Assuming CGI script.+$home\/$moniker\.cgi/ => "default $moniker.cgi"; 37 | like $buffer => qr/write.+.htaccess/x => "creating .htaccess"; 38 | my $htaccess = $docroot->child('.htaccess'); 39 | ok -f $htaccess => "created $htaccess"; 40 | 41 | # unlink $home->dirname . '/.htaccess'; 42 | done_testing; 43 | 44 | -------------------------------------------------------------------------------- /t/011_novy_dom.t: -------------------------------------------------------------------------------- 1 | use open qw(:std :utf8); 2 | use Mojo::Base -strict; 3 | use FindBin qw($Bin); 4 | use Test::More; 5 | use Test::Mojo; 6 | use Mojo::File qw(path); 7 | use Mojo::Util qw(decode); 8 | my $t = Test::Mojo->with_roles('+Slovo')->install( 9 | 10 | # from => to 11 | # "$Bin/.." => '/home/berov/opt/t.com/slovo', 12 | # 0777 13 | )->new('Slovo'); 14 | my $app = $t->app; 15 | my $moniker = $app->moniker; 16 | my $mode = $app->mode; 17 | my $home = $app->home; 18 | note $home; 19 | my $COMMAND = 'Slovo::Command::Author::generate::novy_dom'; 20 | require_ok($COMMAND); 21 | my $command = $COMMAND->new(app => $app); 22 | isa_ok($command => 'Slovo::Command'); 23 | 24 | # Default values 25 | my $buffer = ''; 26 | my $db_file = $app->home->child("data/$moniker.$mode.sqlite"); 27 | subtest 'Default values' => sub { 28 | { 29 | open my $handle, '>', \$buffer; 30 | local *STDOUT = $handle; 31 | $command->run(); 32 | } 33 | like $buffer => qr/Domain.+mandatory\sargument/x => 'domains folder is mandatory'; 34 | like $buffer => qr/Usage/x => 'help is displayed'; 35 | ok($db_file->stat, 'database is created on the first run'); 36 | 37 | # note $buffer; 38 | note '---------------------------'; 39 | }; 40 | 41 | # Custom values 42 | subtest 'Domain name' => sub { 43 | 44 | $buffer = ''; 45 | { 46 | open my $handle, '>', \$buffer; 47 | local *STDOUT = $handle; 48 | $command->run('-name' => 't.com'); 49 | require Slovo::Command::Author::generate::cgi_script; 50 | Slovo::Command::Author::generate::cgi_script->new(app => $app)->run; 51 | require Slovo::Command::Author::generate::a2htaccess; 52 | Slovo::Command::Author::generate::a2htaccess->new(app => $app)->run; 53 | close $handle; 54 | } 55 | 56 | # note $buffer; 57 | like $buffer => qr/mkdir.+\/t\.com$/mx => 'domain folder created'; 58 | like $buffer => qr/mkdir.+\/t\.com\/public/x => 'public folder created'; 59 | like $buffer => qr/mkdir.+\/t\.com\/templates/x => 'templates folder created'; 60 | like $buffer => qr/write.+\/t\.com\/.+\/_form\.html/x => 'templates copied'; 61 | like $buffer => qr/write.+\/t\.com\/.+\/fonts.css/x => 'static files copied'; 62 | like $buffer => qr/"site_name" => "t.com"/ => 'new domain record'; 63 | like $buffer => qr/Assuming\sdomain\sal.+dev.t.com/x => 'default domain prefixes'; 64 | }; 65 | 66 | # Domain Aliases 67 | subtest 'Custom aliases' => sub { 68 | $buffer = ''; 69 | { 70 | open my $handle, '>', \$buffer; 71 | local *STDOUT = $handle; 72 | $command->run( 73 | '--name' => 't1.com', 74 | '--aliases' => 't2.com,t3.com', 75 | '--skip' => '\.css$' 76 | ); 77 | require Slovo::Command::Author::generate::cgi_script; 78 | Slovo::Command::Author::generate::cgi_script->new(app => $app)->run; 79 | require Slovo::Command::Author::generate::a2htaccess; 80 | Slovo::Command::Author::generate::a2htaccess->new(app => $app)->run; 81 | } 82 | 83 | like $buffer => qr/(?:mkdir|exist).+\/t1\.com$/mx => 'domain folder created'; 84 | like $buffer => qr/Domain\salia.+t2.com.+dev.t1.com/x => 'custom aliases'; 85 | unlike $buffer => qr/\.css$/ms => 'skip ' . $command->skip_qr; 86 | }; 87 | 88 | subtest 'Skip and refresh files' => sub { 89 | 90 | $buffer = ''; 91 | my $command; 92 | { 93 | open my $handle, '>', \$buffer; 94 | local *STDOUT = $handle; 95 | $command = $app->commands->run( 96 | generate => 'novy_dom', 97 | '--name' => 't.com', 98 | '--skip' => '\.css$', 99 | '--refresh' => 'partials/.+\.ep$' 100 | ); 101 | } 102 | like $buffer => qr/unlink.+partials.+\.html\.ep/ => 'unlink ' . $command->refresh_qr; 103 | like $buffer => qr/\[write\].+partials.+\.html\.ep/ => 'refresh ' 104 | . $command->refresh_qr; 105 | unlike $buffer => qr/\.css$/ms => 'skip ' . $command->skip_qr; 106 | 107 | # note $buffer; 108 | # note '---------------------------'; 109 | }; 110 | done_testing; 111 | 112 | -------------------------------------------------------------------------------- /t/013_default_helpers.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use Test::More; 3 | use Test::Mojo; 4 | use Mojo::Util qw(punycode_encode); 5 | use Mojo::Collection 'c'; 6 | 7 | my $t = Test::Mojo->with_roles('+Slovo')->install()->new('Slovo'); 8 | my $app = $t->app; 9 | my $c = $app->build_controller; 10 | $c->req->headers->host('xn--' . punycode_encode('алабала') . '.com:3000'); 11 | is $c->host_only => 'xn--80aaaad2dd.com', 'right host'; 12 | is $c->ihost_only => 'алабала.com', 'right ihost'; 13 | is $c->is_user_authenticated => '', 'is_user_authenticated: no'; 14 | is $c->languages->first => c(@{$c->openapi_spec('/parameters/language/enum')})->first, 15 | 'languages'; 16 | is $c->language => 'bg-bg', 'get right language'; 17 | is $c->language('sr')->language => 'sr', 'set right language'; 18 | is $c->language('ua')->language => $c->languages->first, 'set wrong language'; 19 | ok ref $app->renderer->helpers->{debug} eq 'CODE' => '$c->debug exists'; 20 | 21 | done_testing(); 22 | -------------------------------------------------------------------------------- /t/015_generate_list.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict; 2 | use FindBin qw($Bin); 3 | use Test::More; 4 | use Test::Mojo; 5 | use Mojo::File qw(path); 6 | use Mojo::Util qw(decode); 7 | my $t = Test::Mojo->with_roles('+Slovo')->install( 8 | 9 | # from => to 10 | # "$Bin/.." => '/home/berov/opt/t.com/slovo', 11 | # 0777 12 | )->new('Slovo'); 13 | 14 | my $app = $t->app; 15 | my $COMMAND = 'Slovo::Command::Author::generate'; 16 | require_ok($COMMAND); 17 | my $command = $COMMAND->new(app => $app); 18 | my $GENERATORS = 'Mojolicious::Command::Author::generate'; 19 | isa_ok($command => $GENERATORS); 20 | 21 | ok $command->description, 'has a description'; 22 | like $command->message, qr/generate/, 'has a message'; 23 | like $command->hint, qr/help/, 'has a hint'; 24 | is_deeply $command->namespaces, [$COMMAND, $GENERATORS], 'right namespaces'; 25 | my $buffer = ''; 26 | { 27 | open my $handle, '>', \$buffer; 28 | local *STDOUT = $handle; 29 | local $ENV{HARNESS_ACTIVE} = 0; 30 | $command->run(); 31 | } 32 | for (qw(app dockerfile lite-app makefile plugin)) { 33 | like $buffer => qr|$_| => "$GENERATORS\:\:$_ is listed"; 34 | } 35 | for (qw(cgi-script a2htaccess novy-dom)) { 36 | like $buffer => qr|$_| => "$COMMAND\:\:$_ is listed"; 37 | } 38 | 39 | done_testing; 40 | 41 | -------------------------------------------------------------------------------- /t/data/images/node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/t/data/images/node.png -------------------------------------------------------------------------------- /t/data/images/perl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/t/data/images/perl.gif -------------------------------------------------------------------------------- /t/data/images/perl6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kberov/Slovo/b9084b0ac2bd9de68ba7c945fa1fda97641df24b/t/data/images/perl6.jpeg -------------------------------------------------------------------------------- /t/manifest.t: -------------------------------------------------------------------------------- 1 | # manifest.t 2 | use strict; 3 | use warnings FATAL => 'all'; 4 | use Test::More; 5 | 6 | unless ($ENV{TEST_AUTHOR}) { 7 | plan(skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to a true value to run.'); 8 | } 9 | 10 | require ExtUtils::Manifest; 11 | is_deeply [ExtUtils::Manifest::manicheck()], [], 'missing'; 12 | is_deeply [ExtUtils::Manifest::filecheck()], [], 'extra'; 13 | 14 | done_testing(); 15 | -------------------------------------------------------------------------------- /t/perl-critic.t: -------------------------------------------------------------------------------- 1 | # t/perl-critic.t 2 | use Mojo::Base -strict; 3 | use Test::More; 4 | use English qw(-no_match_vars); 5 | use File::Basename; 6 | if (not $ENV{TEST_AUTHOR}) { 7 | my $msg = 'Author test. Set $ENV{TEST_AUTHOR} to a true value to run.'; 8 | plan(skip_all => $msg); 9 | } 10 | 11 | eval { require Test::Perl::Critic; }; 12 | 13 | if ($EVAL_ERROR) { 14 | my $msg = 'Test::Perl::Critic required to criticise code'; 15 | plan(skip_all => $msg); 16 | } 17 | 18 | my $rcfile = dirname(__FILE__) . '/../.perlcriticrc'; 19 | 20 | Test::Perl::Critic->import(-profile => $rcfile, -verbose => 10); 21 | all_critic_ok(); 22 | 23 | -------------------------------------------------------------------------------- /t/perltidy.t: -------------------------------------------------------------------------------- 1 | use Mojo::Base -strict, -signatures; 2 | use Mojo::File 'path'; 3 | use Test::More; 4 | 5 | if (not $ENV{TEST_AUTHOR}) { 6 | my $msg = 'Author test. Set $ENV{TEST_AUTHOR} to a true value to run.'; 7 | plan(skip_all => $msg); 8 | } 9 | 10 | eval { require Test::PerlTidy; } or do { 11 | plan(skip_all => 'Test::PerlTidy required to criticise code'); 12 | }; 13 | 14 | my $ROOT = path(__FILE__)->dirname->dirname->to_string; 15 | Test::PerlTidy::run_tests( 16 | 17 | #debug => 1, 18 | path => $ROOT, 19 | exclude => ['local/', 'blib/', 'data/', 'lib/perl5/', 'domove'], 20 | perltidyrc => "$ROOT/.perltidyrc" 21 | ); 22 | 23 | -------------------------------------------------------------------------------- /t/pod.t: -------------------------------------------------------------------------------- 1 | #!perl -T 2 | use strict; 3 | use warnings FATAL => 'all'; 4 | use Test::More; 5 | 6 | unless ($ENV{TEST_AUTHOR}) { 7 | plan(skip_all => 'Author test. Set $ENV{TEST_AUTHOR} to a true value to run.'); 8 | } 9 | 10 | # Ensure a recent version of Test::Pod 11 | my $min_tp = 1.48; 12 | eval "use Test::Pod $min_tp"; 13 | plan skip_all => "Test::Pod $min_tp required for testing POD" if $@; 14 | 15 | all_pod_files_ok(); 16 | --------------------------------------------------------------------------------