├── app ├── source │ ├── styles │ │ ├── update.scss │ │ ├── dashboard.scss │ │ ├── login.scss │ │ ├── partials │ │ │ ├── _nanobar.scss │ │ │ ├── _utilities.scss │ │ │ ├── _se-preview.scss │ │ │ ├── _announce.scss │ │ │ ├── _alertable.scss │ │ │ ├── _layout.scss │ │ │ ├── _loader.scss │ │ │ ├── _panel.scss │ │ │ ├── _badge-cover.scss │ │ │ ├── _locater.scss │ │ │ ├── _variables.scss │ │ │ ├── _editor-toolbar.scss │ │ │ ├── _overrides.scss │ │ │ └── _input-inner-addon.scss │ │ ├── import.scss │ │ ├── admin-toolbar.scss │ │ ├── navigation.scss │ │ ├── editor.scss │ │ ├── lib.scss │ │ ├── tags.scss │ │ ├── users.scss │ │ └── plugins.scss │ ├── templates │ │ ├── tags.new.hbs │ │ ├── posts.new.hbs │ │ ├── users.new.hbs │ │ ├── partials │ │ │ ├── admin-toolbar.hbs │ │ │ ├── locater-items.hbs │ │ │ ├── footer.hbs │ │ │ ├── navigation-item.hbs │ │ │ ├── user-list.hbs │ │ │ ├── tag-list.hbs │ │ │ ├── header.hbs │ │ │ ├── media-list.hbs │ │ │ ├── backups-table.hbs │ │ │ ├── menu.hbs │ │ │ ├── update-table.hbs │ │ │ ├── plugin-list.hbs │ │ │ ├── history-table.hbs │ │ │ └── post-list.hbs │ │ ├── update.hbs │ │ ├── dashboard.hbs │ │ ├── login.recover.hbs │ │ ├── login.reset.hbs │ │ ├── error.system.hbs │ │ ├── maintenance.hbs │ │ ├── login.hbs │ │ ├── plugins.hbs │ │ ├── tags.hbs │ │ ├── users.hbs │ │ ├── uploads.hbs │ │ ├── helpers │ │ │ └── admin.php │ │ └── navigation.hbs │ ├── images │ │ ├── leaf.jpg │ │ ├── note.jpg │ │ ├── autumn.jpg │ │ ├── leaves.jpg │ │ ├── light.jpg │ │ ├── app-icon.png │ │ ├── flowers.jpg │ │ ├── ladybug.jpg │ │ ├── ladybug.png │ │ ├── raccoon.jpg │ │ ├── sunflower.jpg │ │ ├── loader-dark.gif │ │ ├── logo-color.png │ │ ├── loader-light.gif │ │ ├── icon-plus-white.svg │ │ └── icon-pencil-white.svg │ ├── defaults │ │ ├── default.robots.txt │ │ ├── default.database.php │ │ ├── default.htaccess │ │ ├── post.themes.html │ │ ├── post.support.html │ │ ├── post.editor.html │ │ └── post.welcome.html │ ├── config │ │ ├── themes.json │ │ ├── plugins.json │ │ ├── middleware.php │ │ ├── dependencies.php │ │ └── languages.json │ ├── classes │ │ ├── Events │ │ │ ├── ILeafpubEvent.php │ │ │ ├── Tag │ │ │ │ ├── Add.php │ │ │ │ ├── Added.php │ │ │ │ ├── Delete.php │ │ │ │ ├── Deleted.php │ │ │ │ ├── Update.php │ │ │ │ ├── Updated.php │ │ │ │ ├── Retrieve.php │ │ │ │ ├── Retrieved.php │ │ │ │ ├── BeforeRender.php │ │ │ │ ├── ManyRetrieve.php │ │ │ │ └── ManyRetrieved.php │ │ │ ├── Post │ │ │ │ ├── Add.php │ │ │ │ ├── Added.php │ │ │ │ ├── Delete.php │ │ │ │ ├── Update.php │ │ │ │ ├── Deleted.php │ │ │ │ ├── Updated.php │ │ │ │ ├── PostViewed.php │ │ │ │ ├── Retrieve.php │ │ │ │ ├── Retrieved.php │ │ │ │ ├── ManyRetrieve.php │ │ │ │ ├── BeforeRender.php │ │ │ │ └── ManyRetrieved.php │ │ │ ├── User │ │ │ │ ├── Add.php │ │ │ │ ├── Added.php │ │ │ │ ├── Delete.php │ │ │ │ ├── Update.php │ │ │ │ ├── Deleted.php │ │ │ │ ├── Updated.php │ │ │ │ ├── Retrieve.php │ │ │ │ ├── Retrieved.php │ │ │ │ ├── ManyRetrieve.php │ │ │ │ ├── BeforeRender.php │ │ │ │ └── ManyRetrieved.php │ │ │ ├── Upload │ │ │ │ ├── Add.php │ │ │ │ ├── Added.php │ │ │ │ ├── Delete.php │ │ │ │ ├── Deleted.php │ │ │ │ ├── Update.php │ │ │ │ ├── Updated.php │ │ │ │ ├── Retrieve.php │ │ │ │ ├── Retrieved.php │ │ │ │ ├── BeforeRender.php │ │ │ │ ├── ManyRetrieve.php │ │ │ │ ├── ManyRetrieved.php │ │ │ │ ├── SaveImageFile.php │ │ │ │ └── GenerateThumbnail.php │ │ │ ├── Application │ │ │ │ ├── Login.php │ │ │ │ ├── Logout.php │ │ │ │ ├── Startup.php │ │ │ │ ├── MailSend.php │ │ │ │ ├── Shutdown.php │ │ │ │ ├── LeafpubFoot.php │ │ │ │ ├── LeafpubHead.php │ │ │ │ └── MailCompose.php │ │ │ ├── Navigation │ │ │ │ └── AdminMenu.php │ │ │ └── LeafpubEvent.php │ │ ├── Mailer │ │ │ ├── MailerException.php │ │ │ ├── Bridge │ │ │ │ ├── MailerInterface.php │ │ │ │ └── MailMailer.php │ │ │ └── Mail │ │ │ │ ├── AddressFactory.php │ │ │ │ ├── Mail.php │ │ │ │ ├── MailFactory.php │ │ │ │ └── Address.php │ │ ├── Models │ │ │ ├── Tables │ │ │ │ ├── Tag.php │ │ │ │ ├── Post.php │ │ │ │ ├── User.php │ │ │ │ ├── Plugin.php │ │ │ │ ├── Upload.php │ │ │ │ ├── History.php │ │ │ │ ├── PostMeta.php │ │ │ │ ├── PostTags.php │ │ │ │ ├── PostUploads.php │ │ │ │ ├── UploadTags.php │ │ │ │ ├── Setting.php │ │ │ │ ├── TableGateway.php │ │ │ │ └── TruncateTable.php │ │ │ ├── Ddl │ │ │ │ ├── Column │ │ │ │ │ ├── Boolean.php │ │ │ │ │ ├── Longtext.php │ │ │ │ │ └── Enum.php │ │ │ │ ├── Constraint │ │ │ │ │ └── Fulltext.php │ │ │ │ ├── Drop.php │ │ │ │ ├── Setting.php │ │ │ │ ├── PostTags.php │ │ │ │ ├── UploadTags.php │ │ │ │ ├── PostUploads.php │ │ │ │ ├── PostMeta.php │ │ │ │ ├── History.php │ │ │ │ ├── Tag.php │ │ │ │ ├── Upload.php │ │ │ │ ├── User.php │ │ │ │ ├── Plugin.php │ │ │ │ └── Post.php │ │ │ ├── ModelInterface.php │ │ │ ├── AbstractModel.php │ │ │ └── PostMeta.php │ │ ├── Listeners │ │ │ └── Application.php │ │ ├── Controller │ │ │ └── Controller.php │ │ ├── Importer │ │ │ └── ImportFactory.php │ │ ├── Importer.php │ │ ├── Mailer.php │ │ ├── Widget.php │ │ ├── Error.php │ │ └── Theme.php │ ├── scripts │ │ ├── update.js │ │ ├── lib.js │ │ ├── installer.js │ │ ├── login.js │ │ └── modules │ │ │ └── reauthenticate.js │ └── runtime.php ├── backups │ └── .gitignore └── content │ ├── cache │ └── .gitignore │ ├── plugins │ └── .gitignore │ ├── themes │ └── .gitignore │ └── uploads │ ├── 2016 │ └── 10 │ │ ├── leaf.jpg │ │ ├── note.jpg │ │ ├── autumn.jpg │ │ ├── leaves.jpg │ │ ├── light.jpg │ │ ├── flowers.jpg │ │ ├── ladybug.jpg │ │ ├── sunflower.jpg │ │ └── logo-color.png │ └── .gitignore ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── composer.json ├── package.json └── INSTALL.md /app/source/styles/update.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/backups/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /app/content/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /app/content/plugins/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /app/content/themes/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /app/source/templates/tags.new.hbs: -------------------------------------------------------------------------------- 1 | {{> tags.edit}} -------------------------------------------------------------------------------- /app/source/templates/posts.new.hbs: -------------------------------------------------------------------------------- 1 | {{> posts.edit}} -------------------------------------------------------------------------------- /app/source/templates/users.new.hbs: -------------------------------------------------------------------------------- 1 | {{> users.edit}} -------------------------------------------------------------------------------- /app/content/uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !2016/10/** -------------------------------------------------------------------------------- /app/source/styles/dashboard.scss: -------------------------------------------------------------------------------- 1 | // Gridstack 2 | @import "gridstack/src/gridstack"; -------------------------------------------------------------------------------- /app/source/images/leaf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/leaf.jpg -------------------------------------------------------------------------------- /app/source/images/note.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/note.jpg -------------------------------------------------------------------------------- /app/source/defaults/default.robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /admin/ 3 | 4 | Sitemap: {{sitemap_url}} -------------------------------------------------------------------------------- /app/source/images/autumn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/autumn.jpg -------------------------------------------------------------------------------- /app/source/images/leaves.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/leaves.jpg -------------------------------------------------------------------------------- /app/source/images/light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/light.jpg -------------------------------------------------------------------------------- /app/source/images/app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/app-icon.png -------------------------------------------------------------------------------- /app/source/images/flowers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/flowers.jpg -------------------------------------------------------------------------------- /app/source/images/ladybug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/ladybug.jpg -------------------------------------------------------------------------------- /app/source/images/ladybug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/ladybug.png -------------------------------------------------------------------------------- /app/source/images/raccoon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/raccoon.jpg -------------------------------------------------------------------------------- /app/source/images/sunflower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/sunflower.jpg -------------------------------------------------------------------------------- /app/source/images/loader-dark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/loader-dark.gif -------------------------------------------------------------------------------- /app/source/images/logo-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/logo-color.png -------------------------------------------------------------------------------- /app/content/uploads/2016/10/leaf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/leaf.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/note.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/note.jpg -------------------------------------------------------------------------------- /app/source/images/loader-light.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/source/images/loader-light.gif -------------------------------------------------------------------------------- /app/content/uploads/2016/10/autumn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/autumn.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/leaves.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/leaves.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/light.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/flowers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/flowers.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/ladybug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/ladybug.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/sunflower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/sunflower.jpg -------------------------------------------------------------------------------- /app/content/uploads/2016/10/logo-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leafpub/leafpub/HEAD/app/content/uploads/2016/10/logo-color.png -------------------------------------------------------------------------------- /app/source/styles/login.scss: -------------------------------------------------------------------------------- 1 | @import 'partials/variables'; 2 | 3 | .login-form { 4 | max-width: 18rem; 5 | margin: 12vh auto; 6 | } 7 | 8 | .logo { 9 | width: 6rem; 10 | } -------------------------------------------------------------------------------- /app/source/styles/partials/_nanobar.scss: -------------------------------------------------------------------------------- 1 | // Nanobar plugin 2 | .nanobar { 3 | height: 5px; 4 | overflow: hidden; 5 | .bar { 6 | background-color: $leafpub-blue; 7 | } 8 | } -------------------------------------------------------------------------------- /app/source/templates/partials/admin-toolbar.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#each items}} 3 | 4 | {{/each}} 5 |
-------------------------------------------------------------------------------- /app/source/config/themes.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name": "Range", "url": "https://github.com/Leafpub/range"}, 3 | {"name": "Casper", "url": "https://github.com/Leafpub/casper"}, 4 | {"name": "Mapache", "url": "https://github.com/Leafpub/mapache"} 5 | ] -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files 2 | .DS_Store 3 | .htaccess 4 | npm-debug.log 5 | 6 | # Folders 7 | /app/database.php 8 | /app/db_upload_fix.php 9 | /app/source/assets 10 | /app/source/vendor 11 | /app/log 12 | /design 13 | /dist 14 | /node_modules 15 | .vscode -------------------------------------------------------------------------------- /app/source/styles/partials/_utilities.scss: -------------------------------------------------------------------------------- 1 | // Vertical alignment 2 | .valign { 3 | display: table; 4 | width: 100%; 5 | height: 100%; 6 | .valign-middle { 7 | display: table-cell; 8 | vertical-align: middle; 9 | } 10 | } -------------------------------------------------------------------------------- /app/source/config/plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name": "SwiftMailer", "url": "https://github.com/Leafpub/SwiftMailer"}, 3 | {"name": "Latest Posts", "url": "https://github.com/Leafpub/LatestPosts"}, 4 | {"name": "Quick Post", "url": "https://github.com/Leafpub/QuickPost"} 5 | ] -------------------------------------------------------------------------------- /app/source/classes/Events/ILeafpubEvent.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/defaults/default.database.php: -------------------------------------------------------------------------------- 1 | '{{driver}}', 4 | 'host' => '{{host}}', 5 | 'port' => '{{port}}', 6 | 'database' => '{{database}}', 7 | 'user' => '{{user}}', 8 | 'password' => '{{password}}', 9 | 'prefix' => '{{prefix}}', 10 | 'charset' => 'utf8mb4' 11 | ]; -------------------------------------------------------------------------------- /app/source/defaults/default.htaccess: -------------------------------------------------------------------------------- 1 | # Rewrites 2 | RewriteEngine On 3 | 4 | # Uncomment RewriteBase if you are using a subdomain 5 | #RewriteBase / 6 | 7 | # Forbid access to these folders 8 | RewriteRule ^backups/ - [F] 9 | 10 | # Route requests through index 11 | RewriteCond %{REQUEST_FILENAME} !-f 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "jquery": true, 4 | "bitwise": true, 5 | "eqeqeq": true, 6 | "freeze": true, 7 | "latedef": "nofunc", 8 | "newcap": true, 9 | "noarg": true, 10 | "node": true, 11 | "nonew": true, 12 | "regexp": true, 13 | "undef": true, 14 | "unused": true, 15 | "elision": true, 16 | "notypeof": true 17 | } -------------------------------------------------------------------------------- /app/source/styles/import.scss: -------------------------------------------------------------------------------- 1 | @import "partials/variables"; 2 | 3 | .import-form { 4 | padding: 3rem 1rem; 5 | } 6 | 7 | .upload-xml { 8 | transition: all .5s linear; 9 | opacity: 0; 10 | } 11 | 12 | .upload-xml.visible { 13 | opacity: 1; 14 | } 15 | 16 | // XS breakpoint 17 | @include media-breakpoint-down(xs) { 18 | .import-form { 19 | padding: 1.5rem .5rem; 20 | } 21 | } -------------------------------------------------------------------------------- /app/source/images/icon-plus-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/source/classes/Mailer/MailerException.php: -------------------------------------------------------------------------------- 1 | add('Leafpub\Middleware:removeTrailingSlashes'); 5 | $app->add('Leafpub\Middleware:maintenance'); 6 | $app->add('Leafpub\Middleware:updateRegister'); 7 | $app->add('Leafpub\Middleware:imageMiddleware'); 8 | if (LEAFPUB_DEV){ 9 | $app->add(new \RunTracy\Middlewares\TracyMiddleware($app)); 10 | $app->add('Leafpub\Middleware:tracy'); 11 | } 12 | ?> -------------------------------------------------------------------------------- /app/source/templates/partials/locater-items.hbs: -------------------------------------------------------------------------------- 1 | {{#each items}} 2 | 3 | {{#if avatar}} 4 | {{L 'avatar'}} 5 | {{else}} 6 | 7 | {{/if}} 8 | {{title}} 9 | {{#if description}}
{{description}}{{/if}} 10 |
11 | {{/each}} -------------------------------------------------------------------------------- /app/source/classes/Models/Tables/Tag.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/source/classes/Models/Tables/History.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Add.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Added.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Add.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/templates/update.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{!--> menu--}} 4 | 5 |
6 |
7 |
8 |

Database Update

9 |

your database scheme version: {{dbScheme}}

10 |

The actual scheme version: {{schemeVersion}}

11 | Update 12 |
13 |
14 |
15 | 16 | {{> footer}} -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Added.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Delete.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Update.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Delete.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Deleted.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Update.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Updated.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Add.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Added.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Delete.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Update.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Models/Tables/Setting.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Updated.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Retrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Added.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Deleted.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Updated.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/PostViewed.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Retrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/Retrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/Retrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Delete.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Deleted.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Update.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Updated.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Retrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/Retrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/Login.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/Logout.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/ManyRetrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/BeforeRender.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/ManyRetrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Tag/ManyRetrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Retrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/Retrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/ManyRetrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/BeforeRender.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Post/ManyRetrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/BeforeRender.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/User/ManyRetrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/Startup.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Navigation/AdminMenu.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/BeforeRender.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/ManyRetrieve.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/ManyRetrieved.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/MailSend.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/Shutdown.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/SaveImageFile.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/LeafpubFoot.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/LeafpubHead.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Application/MailCompose.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Events/Upload/GenerateThumbnail.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/styles/partials/_se-preview.scss: -------------------------------------------------------------------------------- 1 | .se-preview { 2 | background: #f2f2f2; 3 | border-radius: $border-radius; 4 | padding: 1rem; 5 | margin-bottom: 1rem; 6 | .se-title { 7 | font-size: 1.2rem; 8 | color: $leafpub-blue; 9 | @include text-truncate; 10 | } 11 | .se-url { 12 | font-size: .9rem; 13 | line-height: 1.8; 14 | color: $leafpub-green; 15 | @include text-truncate; 16 | }, 17 | .se-description { 18 | @include text-truncate; 19 | white-space: initial; 20 | } 21 | } -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Column/Boolean.php: -------------------------------------------------------------------------------- 1 | 'DROP TABLE IF EXISTS %1$s' 20 | ]; 21 | } -------------------------------------------------------------------------------- /app/source/classes/Mailer/Bridge/MailerInterface.php: -------------------------------------------------------------------------------- 1 | 3 | 12 | {{/if}} 13 | 14 | 15 | {{admin_scripts}} 16 | 17 | -------------------------------------------------------------------------------- /app/source/classes/Events/LeafpubEvent.php: -------------------------------------------------------------------------------- 1 | _data = $data; 14 | } 15 | 16 | public function getEventData(){ 17 | return $this->_data; 18 | } 19 | 20 | public function setEventData($data){ 21 | $this->_data = $data; 22 | } 23 | } 24 | ?> -------------------------------------------------------------------------------- /app/source/classes/Models/ModelInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 16 | -------------------------------------------------------------------------------- /app/source/classes/Mailer/Bridge/MailMailer.php: -------------------------------------------------------------------------------- 1 | to->getFullAddress(), 22 | $mail->subject, 23 | $mail->message, 24 | 'From: '.$mail->from->getFullAddress() 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/source/classes/Listeners/Application.php: -------------------------------------------------------------------------------- 1 | getEventData(); 18 | // Now, we could load Plugins and add routes... 19 | } 20 | 21 | public function onApplicationShutdown(Shutdown $end){ 22 | //$end->getEventData(); 23 | } 24 | } 25 | ?> -------------------------------------------------------------------------------- /app/source/styles/partials/_announce.scss: -------------------------------------------------------------------------------- 1 | .announce { 2 | position: fixed; 3 | z-index: 9999; 4 | top: 0; 5 | width: 34rem; 6 | left: calc(50% - 17rem); 7 | padding: 1.1rem; 8 | background: $brand-primary; 9 | border-bottom-left-radius: $border-radius; 10 | border-bottom-right-radius: $border-radius; 11 | color: white; 12 | text-align: center; 13 | pointer-events: none; 14 | &.announce-info { 15 | background-color: $brand-info; 16 | } 17 | &.announce-success { 18 | background-color: $brand-success; 19 | } 20 | &.announce-warning { 21 | background-color: $brand-warning; 22 | } 23 | &.announce-danger { 24 | background-color: $brand-danger; 25 | } 26 | } 27 | 28 | // XS breakpoint 29 | @include media-breakpoint-down(xs) { 30 | .announce { 31 | width: 100%; 32 | left: 0; 33 | border-radius: 0; 34 | padding: .75rem; 35 | } 36 | } -------------------------------------------------------------------------------- /app/source/styles/partials/_alertable.scss: -------------------------------------------------------------------------------- 1 | // Alertable plugin 2 | .alertable { 3 | position: fixed; 4 | z-index: 9999; 5 | // top: vertically centered by script 6 | left: calc(50% - 12.5rem); 7 | width: 25rem; 8 | background: white; 9 | border-radius: $border-radius; 10 | box-shadow: 0 0 2.5rem rgba(black, .1); 11 | padding: 1.5rem; 12 | margin: 0 auto; 13 | } 14 | 15 | .alertable-overlay { 16 | position: fixed; 17 | z-index: 9998; 18 | top: 0; 19 | right: 0; 20 | bottom: 0; 21 | left: 0; 22 | background: rgba(black, .3); 23 | } 24 | 25 | .alertable-message { 26 | margin-bottom: 1rem; 27 | } 28 | 29 | .alertable-prompt { 30 | margin-bottom: 1rem; 31 | } 32 | 33 | .alertable-buttons { 34 | text-align: right; 35 | } 36 | 37 | .alertable button { 38 | margin-left: .25rem; 39 | } 40 | 41 | @include media-breakpoint-down(xs) { 42 | .alertable { 43 | left: 4%; 44 | width: 92%; 45 | } 46 | } -------------------------------------------------------------------------------- /app/source/classes/Mailer/Mail/AddressFactory.php: -------------------------------------------------------------------------------- 1 | ci = $ci; 25 | } 26 | 27 | protected function notFound($request, $response) { 28 | $this->ci['notFoundHandler']($request, $response); 29 | return $response->withStatus(404); 30 | } 31 | 32 | protected function returnJson($request){ 33 | return (bool) ($request->getParam('returnJson') != null); 34 | } 35 | 36 | public function __get($name){ 37 | return $this->ci->get($name); 38 | } 39 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php": ">=7.1", 4 | "ext-curl": "*", 5 | "ext-gd": "*", 6 | "ext-mbstring": "*", 7 | "ext-openssl": "*", 8 | "ext-pdo_mysql": "*", 9 | "zordius/lightncandy": "^1.0", 10 | "firebase/php-jwt": "^3.0", 11 | "slim/slim": "^3.0", 12 | "claviska/simpleimage": "^3.0", 13 | "erusev/parsedown": "^1.6", 14 | "tinymce/tinymce": "^4.5", 15 | "mpratt/embera": "^1.9", 16 | "symfony/event-dispatcher": "^3.1", 17 | "composer/semver": "^1.4.2", 18 | "zendframework/zend-db": "^2.7", 19 | "monolog/monolog": "^1.0", 20 | "tracy/tracy": "^2.4", 21 | "runcmf/runtracy": "^0.2.9" 22 | }, 23 | "config": { 24 | "vendor-dir": "app/source/vendor" 25 | }, 26 | "scripts": { 27 | "post-update-cmd": [ 28 | "gulp build" 29 | ], 30 | "post-install-cmd": [ 31 | "npm install", 32 | "gulp build" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/source/classes/Mailer/Mail/Mail.php: -------------------------------------------------------------------------------- 1 | to = $to; 42 | $this->from = $from; 43 | $this->subject = $subject; 44 | $this->message = $message; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Setting.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Varchar('name', 191), 23 | new Column\Longtext('value') 24 | ]; 25 | 26 | $this->constraints = [ 27 | new \Zend\Db\Sql\Ddl\Constraint\UniqueKey('name') 28 | ]; 29 | } 30 | 31 | public function setTable($t){ 32 | return $this; 33 | } 34 | 35 | public function getTable(){ 36 | return $this->table; 37 | } 38 | } -------------------------------------------------------------------------------- /app/source/config/dependencies.php: -------------------------------------------------------------------------------- 1 | withStatus(404)->write(Error::render()); 12 | }; 13 | }; 14 | 15 | // Not allowed handler 16 | $container['notAllowedHandler'] = function($container) { 17 | return function($request, $response, $methods) use ($container) { 18 | return $response->withStatus(405)->write(Error::system([ 19 | 'title' => 'Method Not Allowed', 20 | 'message' => 'Method must be one of: ' . implode(', ', $methods) 21 | ])); 22 | }; 23 | }; 24 | 25 | // Error handlers 26 | $container['errorHandler'] = function($container) { 27 | return function($request, $response, $exception) use ($container) { 28 | return $response->withStatus(500)->write(Error::system([ 29 | 'title' => 'Application Error', 30 | 'message' => $exception->getMessage() 31 | ])); 32 | }; 33 | }; 34 | 35 | ?> -------------------------------------------------------------------------------- /app/source/config/languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"name": "Dansk", "code": "da-dk", "url": "https://github.com/Leafpub/da-dk"}, 3 | {"name": "Deutsch", "code": "de-de", "url": "https://github.com/Leafpub/de-de"}, 4 | {"name": "Español", "code": "es", "url": "https://github.com/Leafpub/es"}, 5 | {"name": "Français (FR)", "code": "fr-fr", "url": "https://github.com/Leafpub/fr-fr"}, 6 | {"name": "Italiano (IT)", "code": "it-it", "url": "https://github.com/Leafpub/it-it"}, 7 | {"name": "日本語 (JP)", "code": "jp-jp", "url": "https://github.com/Leafpub/jp-jp"}, 8 | {"name": "Norwegian Bokmål (NO)", "code": "nb-bo", "url": "https://github.com/Leafpub/nb-bo"}, 9 | {"name": "Nederlands", "code": "nl-nl", "url": "https://github.com/Leafpub/nl-nl"}, 10 | {"name": "Polski", "code": "pl", "url": "https://github.com/Leafpub/pl"}, 11 | {"name": "Português Brasil (BR)", "code": "pt-br", "url": "https://github.com/Leafpub/pt-br"}, 12 | {"name": "Português (PT)", "code": "pt-pt", "url": "https://github.com/Leafpub/pt-pt"}, 13 | {"name": "Türkçe", "code": "tr-tr", "url": "https://github.com/Leafpub/tr-tr"} 14 | ] 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Pull Request Summary 2 | 3 | Tell us why you’re submitting this PR. 4 | 5 | ### Have you... 6 | 7 | - [ ] Checked that your code adheres to our code guidelines? 8 | 9 | --- 10 | 11 | ### Contributor License Agreement 12 | 13 | We want Leafpub to remain free and open for everyone to use forever, so please don’t submit any code or content that is licensed or copyrighted without first getting written consent. 14 | 15 | Before we accept your pull request, we ask that you agree to the following terms to ensure there are no conflicts with Leafpub’s license now or in the future. 16 | 17 | 1. I certify that the code I am contributing to this project is completely original or free of any known license or copyright. 18 | 19 | 2. I agree to allow the developer of Leafpub license or relicense my work at their discretion. By submitting this pull request, I agree to forfeit all copyrights, intellectual property rights, and royalties pertaining to my contribution. 20 | 21 | 3. By entering my name in the space provided below, I agree to all of the terms mentioned above. 22 | 23 | Signed: [FULL_LEGAL_NAME] 24 | 25 | Date: [YYYY-MM-DD] -------------------------------------------------------------------------------- /app/source/classes/Importer/ImportFactory.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/PostTags.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('post'), 23 | new \Zend\Db\Sql\Ddl\Column\Integer('tag') 24 | ]; 25 | 26 | $this->constraints = [ 27 | new \Zend\Db\Sql\Ddl\Index\Index('post'), 28 | new \Zend\Db\Sql\Ddl\Index\Index('tag') 29 | ]; 30 | } 31 | 32 | public function setTable($t){ 33 | return $this; 34 | } 35 | 36 | public function getTable(){ 37 | return $this->table; 38 | } 39 | } -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/UploadTags.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('upload'), 23 | new \Zend\Db\Sql\Ddl\Column\Integer('tag') 24 | ]; 25 | 26 | $this->constraints = [ 27 | new \Zend\Db\Sql\Ddl\Index\Index('tag'), 28 | new \Zend\Db\Sql\Ddl\Index\Index('upload') 29 | ]; 30 | } 31 | 32 | public function setTable($t){ 33 | return $this; 34 | } 35 | 36 | public function getTable(){ 37 | return $this->table; 38 | } 39 | } -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/PostUploads.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('post'), 23 | new \Zend\Db\Sql\Ddl\Column\Integer('upload') 24 | ]; 25 | 26 | $this->constraints = [ 27 | new \Zend\Db\Sql\Ddl\Index\Index('post'), 28 | new \Zend\Db\Sql\Ddl\Index\Index('upload') 29 | ]; 30 | } 31 | 32 | public function setTable($t){ 33 | return $this; 34 | } 35 | 36 | public function getTable(){ 37 | return $this->table; 38 | } 39 | } -------------------------------------------------------------------------------- /app/source/classes/Mailer/Mail/MailFactory.php: -------------------------------------------------------------------------------- 1 | truncate(); 25 | } 26 | 27 | /** 28 | * Checks if the caller is allowed to call a method 29 | * 30 | * @return bool 31 | */ 32 | 33 | protected static function isAllowedCaller(){ 34 | if ($_REQUEST['cmd'] === 'install') return true; 35 | $data = debug_backtrace(); 36 | return in_array($data[2]['class'], static::$allowedCaller); 37 | } 38 | } -------------------------------------------------------------------------------- /app/source/classes/Mailer/Mail/Address.php: -------------------------------------------------------------------------------- 1 | email = $email; 29 | $this->name = $name; 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getEmail() { 36 | return $this->email; 37 | } 38 | 39 | /** 40 | * @return string 41 | */ 42 | public function getName() { 43 | return $this->name; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getFullAddress() { 50 | return "{$this->name} <{$this->email}>"; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/source/styles/partials/_layout.scss: -------------------------------------------------------------------------------- 1 | // Top toolbar 2 | .top-toolbar { 3 | @include make-row(); 4 | position: relative; 5 | border-bottom: solid 1px #ddd; 6 | padding-top: .5rem; 7 | padding-bottom: .5rem; 8 | user-select: none; 9 | line-height: 2rem; 10 | h1 { 11 | font-size: 1.3rem; 12 | line-height: inherit; 13 | margin: 0; 14 | @include text-truncate; 15 | i { 16 | color: $text-muted; 17 | margin: 0 .5rem; 18 | } 19 | } 20 | .btn i { 21 | transform: scale(1.2); 22 | } 23 | } 24 | 25 | .top-toolbar-section { 26 | padding-top: .5rem; 27 | padding-bottom: .5rem; 28 | } 29 | 30 | // XS breakpoint 31 | @include media-breakpoint-down(xs) { 32 | .top-toolbar, 33 | .top-toolbar-section { 34 | padding-top: .3rem; 35 | padding-bottom: .3rem; 36 | } 37 | } 38 | 39 | // Main container 40 | .main-container { 41 | @extend .container-fluid; 42 | position: relative; 43 | } 44 | 45 | // Stretch containers (automatically resized in lib.js) 46 | .stretch-down { 47 | overflow-y: auto; 48 | -webkit-overflow-scrolling: touch; 49 | } 50 | 51 | .right { 52 | float: right; 53 | } -------------------------------------------------------------------------------- /app/source/templates/dashboard.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{> menu}} 4 | 5 | {{!-- Toolbar --}} 6 |
7 |
8 | {{!-- Left side --}} 9 |
10 |

11 | {{L 'dashboard'}} 12 |

13 |
14 | {{!-- Right side --}} 15 | 20 |
21 |
22 | 23 | 24 | {{!-- Main container --}} 25 |
26 |
27 | {{!-- Loader --}} 28 |
29 |
30 |

{{L 'rendering_the_dashboard'}}

31 | 32 |
33 |
34 | 35 | 40 |
41 |
42 | 43 | {{> footer}} -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/PostMeta.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('post'), 23 | new \Zend\Db\Sql\Ddl\Column\Varchar('name', 50), 24 | new \Zend\Db\Sql\Ddl\Column\Varchar('value', 191), 25 | new \Zend\Db\Sql\Ddl\Column\Timestamp('created', false, new \Zend\Db\Sql\Expression('NOW()')), 26 | ]; 27 | 28 | $this->constraints = [ 29 | new \Zend\Db\Sql\Ddl\Index\Index('post'), 30 | ]; 31 | } 32 | 33 | public function setTable($t){ 34 | return $this; 35 | } 36 | 37 | public function getTable(){ 38 | return $this->table; 39 | } 40 | } -------------------------------------------------------------------------------- /app/source/templates/partials/user-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each users}} 2 | 8 |
9 | {{#if avatar}} 10 | 11 | {{else}} 12 | 13 | {{/if}} 14 |
15 |
16 | {{name}} 17 | {{#is slug @user.slug}} 18 | 19 | 20 | 21 | {{/is}} 22 |
23 |
24 | {{#is role 'owner'}}{{L 'owner'}}{{/is}} 25 | {{#is role 'admin'}}{{L 'administrator'}}{{/is}} 26 | {{#is role 'editor'}}{{L 'editor'}}{{/is}} 27 | {{#is role 'author'}}{{L 'author'}}{{/is}} 28 |
29 |
30 | {{else}} 31 |
32 |
33 | {{L 'no_users'}} 34 |
35 |
36 | {{/each}} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leafpub", 3 | "version": "1.2.0-beta7", 4 | "private": true, 5 | "devDependencies": { 6 | "del": "^2.2.1", 7 | "fs-extra": "^0.30.0", 8 | "gulp": "^3.9.1", 9 | "gulp-autoprefixer": "^3.1.0", 10 | "gulp-clean-css": "^2.0.12", 11 | "gulp-help": "^1.6.1", 12 | "gulp-imagemin": "^3.0.2", 13 | "gulp-jshint": "^2.0.1", 14 | "gulp-notify": "^2.2.0", 15 | "gulp-preprocess": "^2.0.0", 16 | "gulp-rename": "^1.2.2", 17 | "gulp-sass": "^2.3.2", 18 | "gulp-uglify": "^1.5.4", 19 | "gulp-watch": "^4.3.9", 20 | "jshint": "^2.9.2", 21 | "jshint-stylish": "^2.2.0", 22 | "path": "^0.12.7" 23 | }, 24 | "dependencies": { 25 | "@claviska/jquery-alertable": "^1.0.2", 26 | "@claviska/jquery-offscreen": "^1.0.1", 27 | "@claviska/jquery-selectable": "^1.0.3", 28 | "bootstrap": "^4.0.0", 29 | "font-awesome": "^4.6.3", 30 | "gridstack": "^0.2.6", 31 | "jquery": "^3.1.0", 32 | "jquery-ui": "^1.12.1", 33 | "js-cookie": "^2.1.2", 34 | "lodash": "^4.14.1", 35 | "nanobar": "^0.4.2", 36 | "popper.js": "^1.14.1", 37 | "selectize": "^0.12.2", 38 | "showdown": "^1.4.2", 39 | "slugify": "^1.1.0", 40 | "sortablejs": "^1.4.2", 41 | "tether": "^1.3.2", 42 | "velocity-animate": "^1.2.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/source/styles/partials/_loader.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Loaders - simple loader utility 4 | 5 | Basic loader (inherits 1em text size): 6 | 7 | 8 | 9 | Loader on dark background: 10 | 11 | 12 | 13 | Loaders with specific sizes: 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | */ 22 | 23 | .loader { 24 | display: inline-block; 25 | background-image: url(../img/loader-light.gif); 26 | background-repeat: no-repeat; 27 | background-position: center; 28 | background-size: contain; 29 | vertical-align: middle; 30 | font-size: inherit; 31 | height: 1em; 32 | width: 1em; 33 | &.loader-dark { 34 | background-image: url(../img/loader-dark.gif); 35 | } 36 | &.loader-xs { 37 | font-size: 16px; 38 | } 39 | &.loader-sm { 40 | font-size: 24px; 41 | } 42 | &.loader-md { 43 | font-size: 32px; 44 | } 45 | &.loader-lg { 46 | font-size: 48px; 47 | } 48 | &.loader-xl { 49 | font-size: 64px; 50 | } 51 | &.loader-xxl { 52 | font-size: 96px; 53 | } 54 | } -------------------------------------------------------------------------------- /app/source/templates/partials/tag-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each tags}} 2 | 8 | {{#if cover}} 9 | 10 | {{/if}} 11 | 12 | 13 | {{#is type 'post'}} 14 | {{number (post_count tag=slug status=null ignore_pages=false end_date=null)}} 15 | {{else}} 16 | {{number (upload_count tag=slug )}} 17 | {{/is}} 18 | 19 |
20 | {{name}} 21 |
22 |
23 | {{!--#if cover}} 24 | 25 | {{/if--}} 26 | {{text (markdown description)}} 27 |
28 |
29 | {{else}} 30 |
31 |
32 | {{L 'no_tags'}} 33 |
34 |
35 | {{/each}} -------------------------------------------------------------------------------- /app/source/templates/login.recover.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 | 22 |
23 |
24 |
25 |
26 | {{L 'cancel'}} 27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 | {{> footer}} -------------------------------------------------------------------------------- /app/source/scripts/lib.js: -------------------------------------------------------------------------------- 1 | // Libs 2 | // include node_modules/jquery/dist/jquery.min.js 3 | // @include app/source/scripts/modules/jquery-ui.min.js 4 | // @include node_modules/tether/dist/js/tether.min.js 5 | // @include node_modules/popper.js/dist/umd/popper.js 6 | // @include node_modules/bootstrap/dist/js/bootstrap.js 7 | // @include node_modules/nanobar/nanobar.min.js 8 | // @include node_modules/selectize/dist/js/standalone/selectize.min.js 9 | // @include node_modules/js-cookie/src/js.cookie.js 10 | // @include node_modules/showdown/dist/showdown.min.js 11 | // @include node_modules/sortablejs/Sortable.min.js 12 | // @include node_modules/velocity-animate/velocity.min.js 13 | // @include node_modules/velocity-animate/velocity.ui.min.js 14 | // @include node_modules/@claviska/jquery-alertable/jquery.alertable.min.js 15 | // @include node_modules/@claviska/jquery-offscreen/jquery.offscreen.js 16 | // @include node_modules/@claviska/jquery-selectable/jquery.selectable.min.js 17 | // @include node_modules/lodash/lodash.min.js 18 | // @include node_modules/gridstack/dist/gridstack.min.js 19 | // @include node_modules/slugify/index.js 20 | 21 | // Modules 22 | // @include app/source/scripts/modules/leafpub.js 23 | // @include app/source/scripts/modules/locater.js 24 | // @include app/source/scripts/modules/reauthenticate.js 25 | // @include app/source/scripts/modules/shared.js -------------------------------------------------------------------------------- /app/source/templates/login.reset.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 27 |
28 | 29 | 30 |
31 | 32 | {{> footer}} -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/History.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 19 | 20 | $this->columns = [ 21 | new \Zend\Db\Sql\Ddl\Column\Integer('id', false, null, ['auto_increment' => true]), 22 | new \Zend\Db\Sql\Ddl\Column\Integer('post'), 23 | new \Zend\Db\Sql\Ddl\Column\Datetime('rev_date'), 24 | new Column\Longtext('post_data'), 25 | new \Zend\Db\Sql\Ddl\Column\Binary('initial', null, false, 0), 26 | ]; 27 | 28 | $this->constraints = [ 29 | new \Zend\Db\Sql\Ddl\Constraint\PrimaryKey('id'), 30 | new \Zend\Db\Sql\Ddl\Index\Index('post'), 31 | ]; 32 | } 33 | 34 | public function setTable($t){ 35 | return $this; 36 | } 37 | 38 | public function getTable(){ 39 | return $this->table; 40 | } 41 | } -------------------------------------------------------------------------------- /app/source/classes/Importer.php: -------------------------------------------------------------------------------- 1 | 'maintenance', 'value' => 'off']); 15 | // 2. ini_set 16 | $old_execution_time = ini_get('max_execution_time'); 17 | ini_set('max_execution_time', '600'); 18 | try { 19 | // 5. Parse file and import data 20 | $importer = ImportFactory::factory($dropin, $file); 21 | $importer->setOptions($options); 22 | $importer->parseFile(); 23 | $ret = $importer->importData(); 24 | // 8. unlink $file 25 | unlink($file); 26 | // 9. Set maintenance mode off 27 | ini_set('max_execution_time', $old_execution_time); 28 | Models\Setting::edit(['name' => 'maintenance', 'value' => 'off']); 29 | return $ret; 30 | } catch (\Exception $e){ 31 | self::getLogger()->error($e->getMessage()); 32 | return [ 33 | 'succeed' => [], 34 | 'failed' => [['error', $e->getMessage()]] 35 | ]; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /app/source/templates/partials/header.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} · {{@settings.title}} 5 | 6 | 7 | 8 | 9 | {{!-- Leafpub metadata --}} 10 | 11 | 12 | 13 | {{!-- App icons --}} 14 | {{#either @settings.favicon 'source/assets/img/logo-color.png'}} 15 | 16 | 17 | {{/either}} 18 | {{!-- Styles --}} 19 | 20 | {{admin_styles}} 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/source/defaults/post.themes.html: -------------------------------------------------------------------------------- 1 |

Themes

2 |

Leafpub templates are built with a simple but powerful template system called Handlebars. If you've never used Handlebars before, you're in for a treat.

3 |

Handlebar templates are easy to create and don't require any PHP programming whatsoever.

4 |

The easiest way to get started is to open the .hbs files in content/themes/range and play around with them. They're just HTML files with {{placeholders}}, so it won't be long before you're building your own themes. For more info about themes, check out these posts.

5 |

Tip: You might need to configure your text editor to open .hbs files.

6 |

Plugins

7 |

Plugins are able to extend Leafpub in many ways. 
They can be a middleware or subscribe to any Leafpub event that gets fired.
It's also possible to define own routes and create new endpoints.

8 |

For more infos visit the docs section.

9 |

 

10 |

Let me know how you'd like to extend Leafpub by tweeting to @leafpub!

-------------------------------------------------------------------------------- /app/source/defaults/post.support.html: -------------------------------------------------------------------------------- 1 |

Leafpub is Free Software

2 |

There's "free" as in freedom and "free" as in beer, and while I very much appreciate both of those things, it's important to remember that even though software may be free, it takes a lot of time and money to develop, maintain, and support.

3 |

I'm a lone developer with a business to run and a family to feed, so I can't spend all my time providing free support for Leafpub. If you need help, first check the docs. If you can't find an answer there, please ask your question on the community forum so we can all benefit.

4 |

Contribute

5 |

This software took me two years to design, develop, and bring to life. If you use Leafpub, please support its development by making a donation. Big or small — every penny will go towards making Leafpub even more amazing.

6 |

To make a contribution, please visit leafpub.org/support.

7 |

Twitter

8 |

Stay up to date by following @leafpub on Twitter. Send a shoutout if you want, but please don't ask for help there. Try the docs or the forum instead.

-------------------------------------------------------------------------------- /app/source/runtime.php: -------------------------------------------------------------------------------- 1 | setPsr4('Leafpub\\', __DIR__ . '/classes'); 14 | $loader->setPsr4('Leafpub\\Plugins\\', __DIR__ . '/../content/plugins'); 15 | $loader->register(); 16 | 17 | // Check PHP version 18 | if(version_compare(PHP_VERSION, '7.1.0') < 0) { 19 | exit('Leafpub requires PHP 7.1 or above.'); 20 | } 21 | 22 | // Check for curl extension 23 | if(!extension_loaded('curl')) { 24 | exit('Leafpub requires the curl extension.'); 25 | } 26 | 27 | // Check for GD extension 28 | if(!extension_loaded('gd')) { 29 | exit('Leafpub requires the GD extension.'); 30 | } 31 | 32 | // Check for mbstring extension 33 | if(!extension_loaded('mbstring')) { 34 | exit('Leafpub requires the Multibyte String extension.'); 35 | } 36 | 37 | // Check for OpenSSL extension 38 | if(!extension_loaded('openssl')) { 39 | exit('Leafpub requires the OpenSSL extension.'); 40 | } 41 | 42 | // Check for PDO MySQL extension 43 | if(!extension_loaded('pdo_mysql')) { 44 | exit('Leafpub requires the PDO extension with the MySQL driver.'); 45 | } 46 | -------------------------------------------------------------------------------- /app/source/templates/error.system.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | 6 | 7 | 8 | 9 | 42 | 43 | 44 |
45 | 46 |

{{title}}

47 |

{{message}}

48 |
{{host}}
49 |
50 | 51 | -------------------------------------------------------------------------------- /app/source/classes/Models/Tables/TableGateway.php: -------------------------------------------------------------------------------- 1 | table = ((empty(self::$prefix)) ? self::DEFAULT_PREFIX : self::$prefix) . $this->table; 21 | 22 | $this->featureSet = new Feature\FeatureSet(); 23 | $this->featureSet->addFeature(new Feature\GlobalAdapterFeature()); 24 | //$this->featureSet->addFeature(new Feature\MetadataFeature()); 25 | //$this->featureSet->addFeature(new Feature\RowGatewayFeature('id')); 26 | 27 | $this->initialize(); 28 | } 29 | 30 | public function showSqlAndExit($select){ 31 | var_dump( 32 | $this->getSql()->getSqlStringForSqlObject($select) 33 | ); 34 | exit; 35 | } 36 | 37 | public function truncate(){ 38 | return $this->getSql()->prepareStatementForSqlObject(new TruncateTable($this->getTable()))->execute(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/source/templates/maintenance.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{@meta.title}} 5 | 6 | 7 | 8 | 9 | 42 | 43 | 44 |
45 | Leafpub logo 46 |

{{L 'maintenance'}}

47 |
{{content}}
48 |
49 | 50 | {{!-- Include the footer from partials/footer.hbs --}} 51 | {{> footer}} -------------------------------------------------------------------------------- /app/source/styles/partials/_panel.scss: -------------------------------------------------------------------------------- 1 | .panel { 2 | position: fixed; 3 | z-index: 1500; 4 | top: 0; 5 | bottom: 0; 6 | width: 35rem; 7 | background: white; 8 | border-left: solid 1px #eee; 9 | box-shadow: -.5rem 0 5rem rgba(black, .05); 10 | padding: 2rem 2rem; 11 | overflow-y: auto; 12 | -webkit-overflow-scrolling: touch; 13 | visibility: hidden; 14 | transition: all $leafpub-transition-speed; 15 | &.panel-right { 16 | right: 0; 17 | left: auto; 18 | transform: translate3d(100%, 0, 0); 19 | } 20 | &.panel-left { 21 | right: auto; 22 | left: 0; 23 | transform: translate3d(-100%, 0, 0); 24 | } 25 | &.active { 26 | visibility: visible; 27 | transform: translate3d(0, 0, 0); 28 | transition-delay: 0s; 29 | } 30 | .close { 31 | position: absolute; 32 | top: 1rem; 33 | right: 1rem; 34 | font-size: 1.2rem; 35 | color: $gray-100; 36 | transition: color $leafpub-transition-speed; 37 | &:hover { 38 | color: $dark; 39 | } 40 | } 41 | h3 { 42 | font-weight: 300; 43 | margin-bottom: 2rem; 44 | } 45 | .nav-tabs { 46 | margin-bottom: 1rem; 47 | } 48 | } 49 | 50 | // SM breakpoint 51 | @include media-breakpoint-down(xs) { 52 | .panel { 53 | width: 100%; 54 | padding: 1rem; 55 | .close { 56 | top: .5rem; 57 | right: .5rem; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/source/styles/admin-toolbar.scss: -------------------------------------------------------------------------------- 1 | @import 'partials/variables'; 2 | 3 | $lp-item-image-size: 24px; 4 | $lp-item-padding: 10px; 5 | $lp-item-width: $lp-item-image-size + ($lp-item-padding * 2); 6 | $lp-item-height: $lp-item-width; 7 | 8 | // Basic reset for toolbar 9 | .lp-toolbar, 10 | .lp-toolbar * { 11 | background: none; 12 | border: none; 13 | box-sizing: border-box; 14 | padding: 0; 15 | margin: 0; 16 | transition: none; 17 | } 18 | 19 | // Toolbar 20 | .lp-toolbar { 21 | position: fixed; 22 | z-index: 9999; 23 | bottom: 20px; 24 | right: 20px; 25 | background: $leafpub-black; 26 | border-radius: $border-radius; 27 | color: white; 28 | opacity: .25; 29 | transition: opacity 0.5s ease-in-out; 30 | &:hover { 31 | opacity: 1; 32 | } 33 | } 34 | 35 | // Toolbar items 36 | .lp-item { 37 | background-repeat: no-repeat; 38 | background-position: center; 39 | background-size: $lp-item-image-size $lp-item-image-size; 40 | color: white; 41 | text-decoration: none; 42 | display: inline-block; 43 | text-align: center; 44 | width: $lp-item-width; 45 | height: $lp-item-height; 46 | line-height: $lp-item-height; 47 | float: left; 48 | transition: .2s transform ease; 49 | &:hover { 50 | color: #09d; 51 | transform: scale(1.2); 52 | } 53 | } 54 | 55 | // Icons via SVG 56 | .lp-home { background-image: url('../img/logo-color.svg'); } 57 | .lp-new-post { background-image: url('../img/icon-plus-white.svg'); } 58 | .lp-edit-post, .lp-edit-author, .lp-edit-tag { background-image: url('../img/icon-pencil-white.svg'); } -------------------------------------------------------------------------------- /app/source/scripts/installer.js: -------------------------------------------------------------------------------- 1 | /* globals Nanobar, Leafpub */ 2 | $(function() { 3 | 'use strict'; 4 | 5 | var progress = new Nanobar(); 6 | 7 | // Submit 8 | $('#installer-form').on('submit', function(event) { 9 | var form = this, 10 | url = $(form).attr('action'); 11 | 12 | event.preventDefault(); 13 | 14 | // Show progress 15 | progress.go(50); 16 | $.ajax({ 17 | url: url, 18 | type: 'POST', 19 | data: $(form).serialize() 20 | }) 21 | .done(function(res) { 22 | if(res.success) { 23 | location.href = res.redirect; 24 | } 25 | 26 | // Show errors 27 | if(res.invalid && res.invalid.length) { 28 | Leafpub.highlightErrors(form, res.invalid); 29 | } 30 | 31 | // Show message 32 | if(res.message) { 33 | $.alertable.alert(res.message, { 34 | ok: 'OK', 35 | cancel: 'Cancel' 36 | }); 37 | } 38 | }) 39 | .always(function() { 40 | // Hide progress 41 | progress.go(100); 42 | }); 43 | }); 44 | 45 | // Force slug syntax for username 46 | $('#username').on('change', function() { 47 | $(this).val( 48 | Leafpub.slug($(this).val()) 49 | ); 50 | }); 51 | 52 | // Strip invalid chars from table prefix 53 | $('#db-prefix').on('change', function() { 54 | $(this).val( 55 | $(this).val().replace(/[^A-Za-z_-]/g, '_') 56 | ); 57 | }); 58 | }); -------------------------------------------------------------------------------- /app/source/templates/partials/media-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each uploads}} 2 |
3 |
4 | 5 |
6 |
7 |
8 |
9 | 10 | {{width}} 11 | 12 | {{height}} 13 |
14 |
15 |
16 | 17 | {{#is posts}} 18 | {{#each posts}} 19 | {{this}}, 20 | {{/each}} 21 | {{else}} 22 | {{L 'no_posts'}} 23 | {{/is}} 24 |
25 |
26 | 27 | {{#is tags}} 28 | {{#each tags}} 29 | {{this}} 30 | {{/each}} 31 | {{else}} 32 | {{L 'no_tags'}} 33 | {{/is}} 34 |
35 |
36 |
37 | {{else}} 38 |
39 |
40 | {{L 'no_uploads'}} 41 |
42 |
43 | {{/each}} -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Tag.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('id', false, null, ['auto_increment' => true]), 23 | new \Zend\Db\Sql\Ddl\Column\Varchar('slug', 191), 24 | new \Zend\Db\Sql\Ddl\Column\Datetime('created'), 25 | new \Zend\Db\Sql\Ddl\Column\Text('name'), 26 | new \Zend\Db\Sql\Ddl\Column\Text('description'), 27 | new \Zend\Db\Sql\Ddl\Column\Text('cover'), 28 | new \Zend\Db\Sql\Ddl\Column\Text('meta_title'), 29 | new \Zend\Db\Sql\Ddl\Column\Text('meta_description'), 30 | new Column\Enum('type', ["'post'", "'upload'"], false, 'post'), 31 | ]; 32 | 33 | $this->constraints = [ 34 | new \Zend\Db\Sql\Ddl\Constraint\PrimaryKey('id'), 35 | new \Zend\Db\Sql\Ddl\Constraint\UniqueKey('slug') 36 | ]; 37 | } 38 | 39 | public function setTable($t){ 40 | return $this; 41 | } 42 | 43 | public function getTable(){ 44 | return $this->table; 45 | } 46 | } -------------------------------------------------------------------------------- /app/source/templates/login.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 |
4 |
5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | 25 | 26 | 27 |
28 |
29 | 30 |
31 | 34 |
35 | 36 |
37 | {{@settings.title}} 38 |
39 | 40 | 41 |
42 | 43 | {{> footer}} -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Upload.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('id', false, null, ['auto_increment' => true]), 23 | new \Zend\Db\Sql\Ddl\Column\Varchar('caption', 191, true), 24 | new \Zend\Db\Sql\Ddl\Column\Datetime('created'), 25 | new \Zend\Db\Sql\Ddl\Column\Varchar('path', 191), 26 | new \Zend\Db\Sql\Ddl\Column\Varchar('filename', 90), 27 | new \Zend\Db\Sql\Ddl\Column\Varchar('extension', 10), 28 | new \Zend\Db\Sql\Ddl\Column\Integer('size'), 29 | new \Zend\Db\Sql\Ddl\Column\Integer('width'), 30 | new \Zend\Db\Sql\Ddl\Column\Integer('height') 31 | ]; 32 | 33 | $this->constraints = [ 34 | new \Zend\Db\Sql\Ddl\Constraint\PrimaryKey('id'), 35 | new \Zend\Db\Sql\Ddl\Constraint\UniqueKey('filename') 36 | ]; 37 | } 38 | 39 | public function setTable($t){ 40 | return $this; 41 | } 42 | 43 | public function getTable(){ 44 | return $this->table; 45 | } 46 | } -------------------------------------------------------------------------------- /app/source/styles/navigation.scss: -------------------------------------------------------------------------------- 1 | @import "partials/variables"; 2 | 3 | .navigation-form { 4 | padding: 2rem 1rem 0 1rem; 5 | } 6 | 7 | .navigation-list-item, 8 | .add-form { 9 | display: table; 10 | width: 100%; 11 | padding-bottom: 1rem; 12 | &.sortable-chosen { 13 | opacity: 1; 14 | } 15 | &.sortable-ghost { 16 | opacity: 0; 17 | } 18 | .nav-col { 19 | vertical-align: middle; 20 | padding: 0 .25rem; 21 | display: table-cell; 22 | &:first-child { 23 | width: 2rem; 24 | } 25 | &:nth-child(2) { 26 | width: 33%; 27 | } 28 | &:nth-child(3) { 29 | width: auto; 30 | } 31 | &:last-child { 32 | width: 2rem; 33 | } 34 | } 35 | .handle { 36 | color: $text-muted; 37 | font-size: 1.1rem; 38 | cursor: move; 39 | } 40 | } 41 | 42 | .add-form { 43 | border-radius: $border-radius; 44 | padding: 0 1rem; 45 | margin-bottom: 1rem; 46 | .nav-col { 47 | &:nth-child(3) { 48 | width: 22%; 49 | } 50 | &nth-child(4) { 51 | width: auto; 52 | } 53 | } 54 | } 55 | 56 | // XS breakpoint 57 | @include media-breakpoint-down(xs) { 58 | .navigation-form { 59 | padding: 1.5rem .5rem; 60 | } 61 | 62 | .add-form { 63 | padding: 1rem; 64 | display: block; 65 | .nav-col:nth-child(n) { 66 | width: 100%; 67 | display: block; 68 | margin-bottom: .5rem; 69 | } 70 | .nav-col:last-child { 71 | margin-bottom: 0; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /app/source/defaults/post.editor.html: -------------------------------------------------------------------------------- 1 |

Leafpub is the first open source publishing platform to feature true WYSIWYG editing. No more forms or rich-text controls — it's just like typing on the live webpage. Here are some tips for using the editor.

2 |

The Toolbar

3 |

The toolbar has all the options you're familiar with. You can format text, insert links or images, and embed content from third-party websites such as Vimeo or Instagram.

4 |

Leafpub strives to produce clean, semantic markup. The idea is to let authors focus on their content, not on how their content looks. That's why you won't find any tools to change fonts, colors, or sizes. Leafpub let's your theme do all the styling and keeps your markup free from inline CSS.

5 |

Drag & Drop

6 |

You can insert images and other files using the Images and Link buttons, respectively. Or just drop a file into the editor. Images will be uploaded and inserted directly into your post, and other files will be uploaded and inserted as a link.

7 |

Settings

8 |

Click the cog button to open the Settings panel. From there, you can fine tune each post from the URL all the way down to metadata.

9 |

Use the History tab to view all a post's past revisions. One click will show you a preview of the post at any given time. Another will revert the post back to that state, including all content, metadata, and settings.

10 |

Zen Mode

11 |

Inline editing is awesome, but sometimes you just want to get in the zone and focus on writing. Leafpub can accommodate you with zen mode. Just click the leaf button and all your styles will disappear so you can become one with your content. Another click will restore Leafpub's incredible inline editing experience.

-------------------------------------------------------------------------------- /app/source/templates/plugins.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{> menu}} 4 | 5 | {{!-- Toolbar --}} 6 |
7 |
8 | {{!-- Left side --}} 9 |
10 |
11 | 12 | 17 |
18 |
19 | {{!-- Right side --}} 20 |
21 |
22 | 25 | 28 |
29 | 33 |
34 |
35 |
36 | 37 | {{!-- Main container --}} 38 |
39 | {{!-- Plugin list --}} 40 |
41 | {{> plugin-list}} 42 |
43 |
44 | 45 | {{> footer}} -------------------------------------------------------------------------------- /app/source/classes/Models/Tables/TruncateTable.php: -------------------------------------------------------------------------------- 1 | /* @lang SQL */ 'TRUNCATE TABLE %1$s', 23 | ]; 24 | 25 | /** 26 | * @var string|TableIdentifier 27 | */ 28 | protected $table = ''; 29 | 30 | /** 31 | * @param null|string|TableIdentifier $table 32 | */ 33 | public function __construct($table = null){ 34 | if ($table) { 35 | $this->table($table); 36 | } 37 | } 38 | 39 | /** 40 | * @param string|TableIdentifier $table 41 | * @return self 42 | */ 43 | public function table($table){ 44 | $this->table = $table; 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param PlatformInterface $platform 50 | * @param DriverInterface|null $driver 51 | * @param ParameterContainer|null $parameterContainer 52 | * @return string 53 | */ 54 | protected function processTruncate(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null){ 55 | return sprintf( 56 | $this->specifications[static::SPECIFICATION_TRUNCATE], 57 | $this->resolveTable($this->table, $platform, $driver, $parameterContainer) 58 | ); 59 | } 60 | } -------------------------------------------------------------------------------- /app/source/templates/tags.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{> menu}} 4 | 5 | {{!-- Toolbar --}} 6 |
7 |
8 | {{!-- Left side --}} 9 |
10 |
11 | 12 | 17 |
18 |
19 | {{!-- Right side --}} 20 |
21 |
22 | 25 | 28 | 31 |
32 | 33 | 34 | 35 |
36 |
37 |
38 | 39 | {{!-- Main container --}} 40 |
41 |
42 | {{!-- Tag list --}} 43 |
44 | {{> tag-list}} 45 |
46 |
47 |
48 | 49 | {{> footer}} -------------------------------------------------------------------------------- /app/source/templates/users.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{> menu}} 4 | 5 | {{!-- Toolbar --}} 6 |
7 |
8 | {{!-- Left side --}} 9 |
10 |
11 | 12 | 17 |
18 |
19 | {{!-- Right side --}} 20 |
21 |
22 | 25 | 28 | 31 |
32 | 33 | 34 | 35 |
36 |
37 |
38 | 39 | {{!-- Main container --}} 40 |
41 |
42 | {{> user-list}} 43 |
44 |
45 | 46 | {{> footer}} -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Column/Enum.php: -------------------------------------------------------------------------------- 1 | setPossibilities($possibilities); 32 | 33 | parent::__construct($name, $nullable, $default, $options); 34 | } 35 | 36 | /** 37 | * @param array $possibilities 38 | * 39 | * @return self 40 | */ 41 | public function setPossibilities($possibilities){ 42 | $this->possibilities = (array) $possibilities; 43 | 44 | return $this; 45 | } 46 | 47 | /** 48 | * @return int 49 | */ 50 | public function getPossibilities(){ 51 | return $this->possibilities; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | protected function getPossibilitiesExpression() { 58 | return (string) implode(',', $this->possibilities); 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public function getExpressionData(){ 65 | $data = parent::getExpressionData(); 66 | 67 | if ($this->getPossibilitiesExpression()) { 68 | $data[0][1][1] .= '(' . $this->getPossibilitiesExpression() . ')'; 69 | } 70 | 71 | return $data; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/source/templates/partials/backups-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if backups}} 2 | 3 | {{#each backups}} 4 | 5 | 12 | 17 | 27 | 38 | 39 | {{/each}} 40 |
6 | {{filename}}
7 | 8 | {{date date format="%d %b %Y"}} · 9 | {{number (math size '/' 1000000)}}MB 10 | 11 |
13 | 18 | 26 | 28 | 36 | 37 |
41 | {{else}} 42 |
43 |
44 | 45 | {{L 'no_backups'}} 46 |
47 |
48 | {{/if}} -------------------------------------------------------------------------------- /app/source/templates/partials/menu.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Main menu --}} 2 | 21 | 22 | {{!-- Mobile menu --}} 23 | -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/User.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 19 | 20 | $this->columns = [ 21 | new \Zend\Db\Sql\Ddl\Column\Integer('id', false, null, ['auto_increment' => true]), 22 | new \Zend\Db\Sql\Ddl\Column\Varchar('slug', 191), 23 | new \Zend\Db\Sql\Ddl\Column\Datetime('created'), 24 | new \Zend\Db\Sql\Ddl\Column\Varchar('name', 191), 25 | new \Zend\Db\Sql\Ddl\Column\Varchar('email', 191), 26 | new \Zend\Db\Sql\Ddl\Column\Varchar('password', 191), 27 | new \Zend\Db\Sql\Ddl\Column\Varchar('reset_token', 191), 28 | new Column\Enum('role', ['\'owner\'','\'admin\'','\'editor\'','\'author\''], false, 'author'), 29 | new \Zend\Db\Sql\Ddl\Column\Text('bio'), 30 | new \Zend\Db\Sql\Ddl\Column\Text('cover'), 31 | new \Zend\Db\Sql\Ddl\Column\Text('avatar'), 32 | new \Zend\Db\Sql\Ddl\Column\Varchar('twitter', 191), 33 | new \Zend\Db\Sql\Ddl\Column\Text('location'), 34 | new \Zend\Db\Sql\Ddl\Column\Text('website') 35 | ]; 36 | 37 | $this->constraints = [ 38 | new \Zend\Db\Sql\Ddl\Constraint\PrimaryKey('id'), 39 | new \Zend\Db\Sql\Ddl\Index\Index('slug'), 40 | ]; 41 | } 42 | 43 | public function setTable($t){ 44 | return $this; 45 | } 46 | 47 | public function getTable(){ 48 | return $this->table; 49 | } 50 | } -------------------------------------------------------------------------------- /app/source/styles/partials/_badge-cover.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | badge Covers - allows you to easily position badges over an element 4 | 5 | Top right badge on the inside of the cover: 6 | 7 |
8 | Markdown 9 | 10 |
11 | 12 | Top right badge on the outside of the cover: 13 | 14 |
15 | Markdown 16 | 17 |
18 | 19 | Available positions: 20 | 21 | Top left - `badge-tl` 22 | Top right - `badge-tr` 23 | Bottom left - `badge-bl` 24 | Bottom right - `badge-br` 25 | 26 | By default, badge are positioned inside the container. Use the `badge-outside` class to 27 | position badges outside the cover. 28 | 29 | */ 30 | 31 | .badge-cover { 32 | position: relative; 33 | font-weight: 300; 34 | .badge { 35 | position: absolute; 36 | // Top left 37 | &.badge-tl { 38 | left: .5em; 39 | top: .5em; 40 | &.badge-outside { 41 | left: 0; 42 | top: -2em; 43 | } 44 | } 45 | // Top right 46 | &.badge-tr { 47 | right: .5em; 48 | top: .5em; 49 | &.badge-outside { 50 | right: 0; 51 | top: -2em; 52 | } 53 | } 54 | // Bottom left 55 | &.badge-bl { 56 | left: .5em; 57 | bottom: .5em; 58 | &.badge-outside { 59 | left: 0; 60 | bottom: -2em; 61 | } 62 | } 63 | // Bottom right 64 | &.badge-br { 65 | right: .5em; 66 | bottom: .5em; 67 | &.badge-outside { 68 | right: 0; 69 | bottom: -2em; 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Plugin.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 19 | 20 | $this->columns = [ 21 | new \Zend\Db\Sql\Ddl\Column\Integer('id', false, null, ['auto_increment' => true]), 22 | new \Zend\Db\Sql\Ddl\Column\Varchar('name', 51), 23 | new \Zend\Db\Sql\Ddl\Column\Varchar('description', 100), 24 | new \Zend\Db\Sql\Ddl\Column\Varchar('author', 51), 25 | new \Zend\Db\Sql\Ddl\Column\Varchar('version', 8), 26 | new \Zend\Db\Sql\Ddl\Column\Varchar('requires', 8), 27 | new \Zend\Db\Sql\Ddl\Column\Varchar('license', 8), 28 | new \Zend\Db\Sql\Ddl\Column\Varchar('dir', 51), 29 | new \Zend\Db\Sql\Ddl\Column\Varchar('img', 100), 30 | new \Zend\Db\Sql\Ddl\Column\Varchar('link', 100), 31 | new \Zend\Db\Sql\Ddl\Column\Binary('isAdminPlugin', null, false, 0), 32 | new \Zend\Db\Sql\Ddl\Column\Binary('isMiddleware', null, false, 0), 33 | new \Zend\Db\Sql\Ddl\Column\Datetime('install_date'), 34 | new \Zend\Db\Sql\Ddl\Column\Binary('enabled', null, false, 0), 35 | new \Zend\Db\Sql\Ddl\Column\Datetime('enable_date') 36 | ]; 37 | 38 | $this->constraints = [ 39 | new \Zend\Db\Sql\Ddl\Constraint\PrimaryKey('id'), 40 | new \Zend\Db\Sql\Ddl\Constraint\UniqueKey('dir') 41 | ]; 42 | } 43 | 44 | public function setTable($t){ 45 | return $this; 46 | } 47 | 48 | public function getTable(){ 49 | return $this->table; 50 | } 51 | } -------------------------------------------------------------------------------- /app/source/templates/partials/update-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if updates}} 2 | {{#if updates.Leafpub}} 3 |

Leafpub

4 | 5 | 6 | 7 | 8 | 9 | 10 |
Update LeafpubNew version: {{updates.Leafpub.newVersion}}
11 | {{/if}} 12 | {{#if updates.plugins}} 13 |

Plugins

14 | 15 | {{#each updates.plugins}} 16 | 17 | 18 | 19 | 20 | 21 | {{/each}} 22 |
Update {{name}}New Version: {{newVersion}}
23 | {{/if}} 24 | {{#if updates.themes}} 25 |

Themes

26 | 27 | {{#each updates.themes}} 28 | 29 | 30 | 31 | 32 | 33 | {{/each}} 34 |
Update {{name}}New Version: {{newVersion}}
35 | {{/if}} 36 | {{#if updates.languages}} 37 |

Languages

38 | 39 | {{#each updates.languages}} 40 | 41 | 42 | 43 | 44 | 45 | {{/each}} 46 |
Update {{name}}New Version: {{newVersion}}
47 | {{/if}} 48 | {{else}} 49 |
50 |
51 | 52 | {{L 'no_updates'}} 53 |
54 |
55 | {{/if}} -------------------------------------------------------------------------------- /app/source/styles/partials/_locater.scss: -------------------------------------------------------------------------------- 1 | .locater-overlay { 2 | position: fixed; 3 | z-index: 2500; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | background: rgba(black, .3); 9 | } 10 | 11 | .locater { 12 | position: fixed; 13 | z-index: 2501; 14 | top: 0; 15 | left: 50%; 16 | margin-left: -$leafpub-locater-width / 2; 17 | width: $leafpub-locater-width; 18 | background: white; 19 | border-bottom-left-radius: $border-radius; 20 | border-bottom-right-radius: $border-radius; 21 | box-shadow: 0 0 2.5rem rgba(black, .2); 22 | padding: 1rem; 23 | .form-group { 24 | margin-bottom: 0; 25 | } 26 | } 27 | 28 | .locater-results { 29 | position: relative; 30 | max-height: 30vh; 31 | border: solid 1px $input-border-color; 32 | border-radius: $border-radius; 33 | margin-top: 1rem; 34 | overflow-y: auto; 35 | a { 36 | @include text-truncate; 37 | position: relative; 38 | color: $body-color; 39 | display: block; 40 | padding: .5rem .5rem .5rem 2rem; 41 | i, 42 | .avatar { 43 | position: absolute; 44 | top: .75rem; 45 | left: .5rem; 46 | color: #ccc; 47 | width: 1.2em; 48 | height: 1.2em 49 | } 50 | .avatar { 51 | border-radius: 50%; 52 | } 53 | small { 54 | color: $text-muted; 55 | } 56 | &:hover, 57 | &:focus { 58 | background-color: #f2f2f2; 59 | text-decoration: none; 60 | } 61 | &.active { 62 | background-color: $leafpub-blue; 63 | color: white; 64 | text-decoration: none; 65 | i, 66 | small { 67 | color: white; 68 | } 69 | } 70 | } 71 | &:empty { 72 | display: none; 73 | } 74 | } 75 | 76 | // XS breakpoint 77 | @include media-breakpoint-down(xs) { 78 | .locater { 79 | width: $leafpub-locater-width-xs; 80 | margin-left: -$leafpub-locater-width-xs / 2; 81 | } 82 | } -------------------------------------------------------------------------------- /app/source/templates/uploads.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{> menu}} 4 | 5 | {{!-- Toolbar --}} 6 |
7 |
8 | {{!-- Left side --}} 9 |
10 |
11 | 12 | 17 |
18 |
19 | {{!-- Right side --}} 20 |
21 |
22 | 25 | 28 | 31 |
32 | 35 |
36 |
37 |
38 | 39 | {{!-- Main container --}} 40 |
41 |
42 | {{!-- media list --}} 43 | 44 | {{> media-list}} 45 | 46 |
47 |
48 | 49 | {{!-- Dropzone --}} 50 | 55 | {{> new-upload}} 56 | {{> footer}} -------------------------------------------------------------------------------- /app/source/styles/editor.scss: -------------------------------------------------------------------------------- 1 | @import 'partials/variables'; 2 | 3 | body { 4 | // Prevent iPhone from rendering huge text when the page is scaled way down 5 | -webkit-text-size-adjust: none; 6 | } 7 | 8 | // You shall not click! 9 | a[href] { 10 | cursor: not-allowed; 11 | } 12 | 13 | // All regions 14 | [data-leafpub-type="post-content"], 15 | [data-leafpub-type="post-title"] { 16 | min-height: 1em; // don't disappear when empty 17 | transition: $leafpub-transition-speed outline-color; 18 | outline: dashed 2px transparent; 19 | outline-offset: 8px; 20 | &.mce-edit-focus { 21 | outline: dashed 2px; 22 | } 23 | &:hover { 24 | outline-color: rgba(black, .25); 25 | } 26 | &:focus { 27 | outline-color: rgba(black, .5); 28 | } 29 | &::after { 30 | content: ''; 31 | display: table; 32 | clear: both; 33 | } 34 | a[href] { 35 | cursor: text; 36 | } 37 | } 38 | 39 | // Post title regions 40 | [data-leafpub-type="post-title"] { 41 | // Prevent empty regions from disappearing 42 | min-width: 1em; 43 | display: inline-block; 44 | } 45 | 46 | // Embeds 47 | [data-embed] { 48 | position: relative !important; 49 | // Create an invisible overlay on top of embedded elements to disable interaction 50 | &:after { 51 | position: absolute; 52 | top: 0; 53 | left: 0; 54 | width: 100%; 55 | height: 100%; 56 | content: ''; 57 | background: $brand-primary; 58 | opacity: 0; 59 | cursor: text; 60 | } 61 | // Highlight the embed when selected 62 | &[data-mce-selected]:after { 63 | opacity: .25; 64 | } 65 | } 66 | 67 | // Hide the ghost container when dragging embeds 68 | .mce-drag-container { 69 | display: none !important; 70 | } 71 | 72 | // Inline boundaries for code and links 73 | a[data-mce-selected], 74 | code[data-mce-selected] { 75 | outline: dashed 1px $leafpub-blue; 76 | } 77 | 78 | // Image selections 79 | img[data-mce-selected] { 80 | outline: none !important; 81 | } 82 | 83 | // HT selections 84 | hr[data-mce-selected] { 85 | outline: dotted 1px black; 86 | } -------------------------------------------------------------------------------- /app/source/styles/lib.scss: -------------------------------------------------------------------------------- 1 | // Leafpub and Bootstrap vars/mixins 2 | @import "partials/variables"; 3 | 4 | // Font Awesome 5 | @import "font-awesome/scss/font-awesome"; 6 | 7 | // Bootstrap 8 | //@import "bootstrap/scss/custom"; 9 | @import "bootstrap/scss/functions"; 10 | @import "bootstrap/scss/variables"; 11 | @import "bootstrap/scss/mixins"; 12 | @import "bootstrap/scss/reboot"; 13 | @import "bootstrap/scss/print"; 14 | @import "bootstrap/scss/reboot"; 15 | @import "bootstrap/scss/type"; 16 | @import "bootstrap/scss/images"; 17 | @import "bootstrap/scss/code"; 18 | @import "bootstrap/scss/grid"; 19 | @import "bootstrap/scss/tables"; 20 | @import "bootstrap/scss/forms"; 21 | @import "bootstrap/scss/buttons"; 22 | // @import "bootstrap/scss/animation"; 23 | @import "bootstrap/scss/dropdown"; 24 | @import "bootstrap/scss/button-group"; 25 | @import "bootstrap/scss/input-group"; 26 | @import "bootstrap/scss/custom-forms"; 27 | @import "bootstrap/scss/nav"; 28 | // @import "bootstrap/scss/navbar"; 29 | @import "bootstrap/scss/card"; 30 | // @import "bootstrap/scss/breadcrumb"; 31 | // @import "bootstrap/scss/pagination"; 32 | @import "bootstrap/scss/badge"; 33 | // @import "bootstrap/scss/jumbotron"; 34 | @import "bootstrap/scss/alert"; 35 | // @import "bootstrap/scss/progress"; 36 | // @import "bootstrap/scss/media"; 37 | @import "bootstrap/scss/list-group"; 38 | // @import "bootstrap/scss/responsive-embed"; 39 | // @import "bootstrap/scss/close"; 40 | // @import "bootstrap/scss/modal"; 41 | @import "bootstrap/scss/tooltip"; 42 | // @import "bootstrap/scss/popover"; 43 | // @import "bootstrap/scss/carousel"; 44 | @import "bootstrap/scss/utilities"; 45 | 46 | // Leafpub overrides and custom partials 47 | @import "partials/announce"; 48 | @import "partials/overrides"; 49 | @import "partials/menu"; 50 | @import "partials/layout"; 51 | @import "partials/alertable"; 52 | @import "partials/editor-toolbar"; 53 | @import "partials/input-inner-addon"; 54 | @import "partials/locater"; 55 | @import "partials/badge-cover"; 56 | @import "partials/loader"; 57 | @import "partials/nanobar"; 58 | @import "partials/panel"; 59 | @import "partials/se-preview"; 60 | @import "partials/selectize"; 61 | @import "partials/utilities"; -------------------------------------------------------------------------------- /app/source/styles/partials/_variables.scss: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Variables 3 | //////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | $leafpub-blue: #09d; 6 | $leafpub-green: #8cc152; 7 | $leafpub-black: #252525; 8 | 9 | $leafpub-transition-speed: .2s; 10 | 11 | // Locater 12 | $leafpub-locater-width: 35rem; 13 | $leafpub-locater-width-xs: 90%; 14 | 15 | // Font awesome path 16 | $fa-font-path: '../font'; 17 | 18 | //////////////////////////////////////////////////////////////////////////////////////////////////// 19 | // Bootstrap variables 20 | //////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | // The basics 23 | $body-color: #333; 24 | $font-size-root: 15px; 25 | //$font-family-sans-serif: "Open Sans", "Helvetica Neue", sans-serif; 26 | //$font-family-monospace: "Roboto Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; 27 | $text-muted: #aaa; 28 | 29 | // Brand 30 | $brand-primary: $leafpub-blue; 31 | $brand-success: $leafpub-green; 32 | $brand-info: #40c9ff; 33 | $brand-warning: #ff851b; 34 | $brand-danger: #ff4136; 35 | 36 | // States 37 | $state-success-bg: $brand-success; 38 | $state-success-text: white; 39 | $state-info-bg: $brand-info; 40 | $state-info-text: white; 41 | $state-warning-bg: $brand-warning; 42 | $state-warning-text: white; 43 | $state-danger-bg: $brand-danger; 44 | $state-danger-text: white; 45 | 46 | // Border radius 47 | $border-radius: 3px; 48 | $border-radius-large: 5px; 49 | $border-radius-small: 2px; 50 | 51 | // Forms 52 | $input-border-color: #ddd; 53 | $input-border-focus: $leafpub-blue; 54 | 55 | // Buttons 56 | $btn-secondary-color: #888; 57 | $btn-secondary-border: #ddd; 58 | 59 | // Alerts 60 | $alert-border-width: 0; 61 | 62 | // Dropdowns 63 | $dropdown-link-hover-color: white; 64 | $dropdown-link-hover-bg: $leafpub-blue; 65 | 66 | 67 | // Tooltips 68 | $tooltip-bg: $leafpub-black; 69 | $tooltip-opacity: 1; 70 | 71 | // Bootstrap vars and mixins 72 | @import "bootstrap/scss/functions"; 73 | @import "bootstrap/scss/variables"; 74 | @import "bootstrap/scss/mixins"; -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Leafpub 2 | 3 | **Simple, beautiful publishing.** 4 | 5 | Created by [Cory LaViska](https://twitter.com/claviska) 6 | Maintained by [Marc Apfelbaum](https://twitter.com/karsasmus) 7 | 8 | - Website: [leafpub.org](https://www.leafpub.org/) 9 | - Twitter: [@leafpub](https://twitter.com/leafpub) 10 | 11 | ## Installation 12 | 13 | 1. Upload everything in this folder to your web server. 14 | 2. Open a web browser and navigation to your domain. 15 | 3. The installer will take care of the rest. 16 | 17 | You can safely delete this file (INSTALL.md) anytime. 18 | 19 | ## Updating 20 | 21 | To update from a previous version, simply replace `index.php` and the `source` folder with the new versions. 22 | 23 | **Important:** If you're updating from 1.0.0, update update your database first using the following statement (you may have to adjust the `leafpub_` prefix): 24 | 25 | ``` 26 | CREATE TABLE `leafpub_plugins` ( 27 | `id` int(11) NOT NULL AUTO_INCREMENT, 28 | `name` varchar(51) NOT NULL, 29 | `description` varchar(100) NOT NULL, 30 | `author` varchar(51) NOT NULL, 31 | `version` varchar(8) NOT NULL, 32 | `requires` varchar(8) NOT NULL, 33 | `license` varchar(8) NOT NULL, 34 | `dir` varchar(51) NOT NULL, 35 | `img` varchar(100) DEFAULT NULL, 36 | `link` varchar(100) DEFAULT NULL, 37 | `isAdminPlugin` tinyint(1) NOT NULL DEFAULT '0', 38 | `isMiddleware` tinyint(1) NOT NULL DEFAULT '0', 39 | `install_date` datetime NOT NULL, 40 | `enabled` tinyint(1) NOT NULL DEFAULT '0', 41 | `enable_date` datetime DEFAULT NULL, 42 | PRIMARY KEY (`id`), 43 | UNIQUE KEY `dir` (`dir`) USING BTREE 44 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 45 | 46 | ``` 47 | 48 | **Important:** If you're updating from 1.0.0-beta3 or below, update your database first using the following statement (you may have to adjust the `leafpub_` prefix): 49 | 50 | ``` 51 | ALTER TABLE `leafpub_posts` ADD `sticky` TINYINT NOT NULL AFTER `featured`; 52 | ALTER TABLE `leafpub_users` ADD `twitter` VARCHAR(191) NOT NULL AFTER `avatar`; 53 | 54 | ``` 55 | 56 | ## License 57 | 58 | Licensed under the terms of the GNU GPLv3. See LICENSE.md for details. 59 | 60 | ©2016 Cory & Marc 61 | 62 | ## Support 63 | 64 | Please visit [leafpub.org/support](https://www.leafpub.org/support) for support. -------------------------------------------------------------------------------- /app/source/defaults/post.welcome.html: -------------------------------------------------------------------------------- 1 |

Hi, I'm Marc, the creator of Leafpub. Thanks for trying it out!

2 |

I spent the last two years building Leafpub because I wanted to bring a better publishing experience to designers, developers, and bloggers everywhere.

3 |

If you enjoy using Leafpub, please pay it forward by telling someone about it or contributing to the project.

4 |

Getting Started

5 |

I've included a few sample posts to help you get started. Please read through each one to get an idea of how things work.

6 |

If you need help, head over to community.leafpub.org for assistance.

7 |

Admin Panel

8 |

To access the admin panel, go to: your-domain.com/admin

9 |

Quick Tips

10 |

Need to find something? Press CMD/CTRL + P to open the locater. You can search for posts, tags, users, and more with just a few keystrokes.

11 |

Want to delete more than one post at a time? Hold down CMD/CTRL and click, or select a range by holding down SHIFT. This works for tags and users too.

12 |

Leafpub saves a copy of every post you publish. From the editor, go to Settings > History to view them. You can also view, restore, and delete revisions from the history panel.

13 |

Save time using keyboard shortcuts. You'll find a list of all available shortcuts in the docs.

14 |

You can backup your entire website — including content, uploads, and themes — by going to Settings > Backups. Leafpub will generate a single .tar file for your convenience.

15 |

Oh, and don't forget to update your profile! Set your avatar and tell us about yourself by filling in your bio. Head over to Users to do that now.

16 |

Looking for a great place to get high quality cover photos? All the ones used in these starter posts are from Unsplash.

-------------------------------------------------------------------------------- /app/source/styles/tags.scss: -------------------------------------------------------------------------------- 1 | @import "partials/variables"; 2 | 3 | .tag-list { 4 | position: relative; 5 | height: 100%; 6 | // margin-left: -$grid-gutter-width / 2; 7 | // margin-right: -$grid-gutter-width / 2; 8 | user-select: none; 9 | width: 100%; 10 | } 11 | 12 | .tag-list-none { 13 | text-align: center; 14 | font-size: 1.3rem; 15 | color: $text-muted; 16 | padding: 1rem; 17 | } 18 | 19 | .tag-list-item { 20 | position: relative; 21 | border-bottom: solid 1px #ddd; 22 | border-left: solid 0 transparent; 23 | background-color: white; 24 | padding: 1rem 1.5rem; 25 | color: $body-color; 26 | overflow: hidden; 27 | transition: 28 | border $leafpub-transition-speed, 29 | padding $leafpub-transition-speed, 30 | background-color $leafpub-transition-speed; 31 | display: block; 32 | .name { 33 | margin-bottom: .3rem; 34 | @include text-truncate; 35 | color: #000; 36 | font-size: 1rem; 37 | font-weight: bold; 38 | } 39 | .description { 40 | font-size: .9rem; 41 | //color: $text-muted; 42 | color: #000; 43 | @include text-truncate; 44 | } 45 | .cover { 46 | //width: 1.4rem; 47 | //height: 1.4rem; 48 | //border-radius: 50%; 49 | background-size: cover; 50 | background-position: center; 51 | vertical-align: middle; 52 | //margin-right: .25rem; 53 | display: inline-block; 54 | position: absolute; 55 | left: 0; 56 | top: 0; 57 | right: 0; 58 | bottom: 0; 59 | opacity: 0.15; 60 | } 61 | .badge { 62 | float: right; 63 | font-size: .8rem; 64 | margin-top: .1rem; 65 | margin-left: .5rem; 66 | } 67 | &:hover { 68 | background-color: #f8f8f8; 69 | color: inherit; 70 | text-decoration: none; 71 | } 72 | &:focus { 73 | color: inherit; 74 | text-decoration: none; 75 | } 76 | &.selected { 77 | background-color: #f2f2f2; 78 | border-left: solid .5rem $leafpub-blue; 79 | padding-right: 1rem; 80 | } 81 | } 82 | 83 | // Fix sticky hovers in iOS 84 | .ios .tag-list-item:hover:not(.selected) { 85 | background-color: inherit; 86 | } -------------------------------------------------------------------------------- /app/source/classes/Mailer.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'name' => 'PHP mail', 23 | 'class' => 'Leafpub\Mailer\Bridge\MailMailer' 24 | ] 25 | ]; 26 | 27 | /** 28 | * @return array 29 | */ 30 | static public function getMailers() { 31 | return self::$mailers; 32 | } 33 | 34 | /** 35 | * @param Mail $mail 36 | * 37 | * @return bool 38 | * @throws MailerException 39 | */ 40 | static public function sendEmail(Mail $mail){ 41 | $mailerClass = self::getMailerClass(Setting::getOne('mailer')); 42 | /** @var MailerInterface $mailer */ 43 | $mailer = new $mailerClass(); 44 | 45 | if (!($mailer instanceof MailerInterface)) { 46 | throw new MailerException("Mailer {$mailerClass} must implement MailerInterface"); 47 | } 48 | 49 | return $mailer->send($mail); 50 | } 51 | 52 | /** 53 | * @param string $mailerName 54 | * 55 | * @throws MailerException 56 | */ 57 | static private function getMailerClass($mailerName) { 58 | foreach(self::$mailers as $name => $mailer) { 59 | if ($mailerName === $name) { 60 | return $mailer['class']; 61 | } 62 | } 63 | throw new MailerException("Given mailer: '$mailerName' doesn't exist"); 64 | } 65 | 66 | public static function addMailer($name, $class){ 67 | if ($name === ''){ 68 | throw new MailerException("Mailer name must be set!"); 69 | } 70 | 71 | if ($class === ''){ 72 | throw new MailerException("Mailer class must be set!"); 73 | } 74 | 75 | self::$mailers[$name] = [ 76 | 'name' => $name, 77 | 'class' => $class 78 | ]; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/source/classes/Widget.php: -------------------------------------------------------------------------------- 1 | widgetName, 'class' => entryClassName] 29 | * @return void 30 | * @throws \Exception 31 | * 32 | */ 33 | public static function addWidget($widgetData){ 34 | static::$widgets[$widgetData['name']] = $widgetData; 35 | } 36 | 37 | /** 38 | * Returns a rendered widget 39 | * 40 | * @param String/array $widgetName 41 | * @return String 42 | * 43 | */ 44 | public static function getWidget($widget){ 45 | $data = []; 46 | if (is_array($widget)){ 47 | $widgetName = $widget['id']; 48 | $data = $widget; 49 | } else { 50 | $widgetName = $widget; 51 | $data['id'] = $widget; 52 | } 53 | 54 | if (isset(self::$widgets[$widgetName])){ 55 | $widgetClass = self::$widgets[$widgetName]['class']; 56 | return $widgetClass::renderWidget($data); 57 | } else { 58 | return false; 59 | } 60 | } 61 | 62 | public static function getWidgets(){ 63 | return self::$widgets; 64 | } 65 | 66 | public static function renderDashboard($userSlug){ 67 | $data = Setting::getOne('dashboard_' . $userSlug); 68 | 69 | if ($data){ 70 | $widgets = json_decode($data, true); 71 | foreach ($widgets as $widget){ 72 | self::getLogger()->debug('Widget Id: ' . $widget->id); 73 | $html = self::getWidget($widget); 74 | if ($html){ 75 | $ret[] = ['widget' => $html]; 76 | } 77 | } 78 | } 79 | 80 | return $ret; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/source/classes/Models/Ddl/Post.php: -------------------------------------------------------------------------------- 1 | table = \Leafpub\Models\Tables\TableGateway::$prefix . $this->table; 20 | 21 | $this->columns = [ 22 | new \Zend\Db\Sql\Ddl\Column\Integer('id', false, null, ['auto_increment' => true]), 23 | new \Zend\Db\Sql\Ddl\Column\Varchar('slug', 191), 24 | new \Zend\Db\Sql\Ddl\Column\Datetime('created', false), //new \Zend\Db\Sql\Expression('NOW()')), NEEDS MySQL 5.6+ 25 | new \Zend\Db\Sql\Ddl\Column\Datetime('pub_date', false), //new \Zend\Db\Sql\Expression('NOW()')), NEEDS MySQL 5.6+ 26 | new \Zend\Db\Sql\Ddl\Column\Integer('author'), 27 | new \Zend\Db\Sql\Ddl\Column\Text('title'), 28 | new Column\Longtext('content'), 29 | new \Zend\Db\Sql\Ddl\Column\Text('image'), 30 | new \Zend\Db\Sql\Ddl\Column\Text('meta_title'), 31 | new \Zend\Db\Sql\Ddl\Column\Text('meta_description'), 32 | new Column\Enum('status', ["'published'", "'draft'"], false, 'published'), 33 | new Column\Boolean('page', null, false, 0), 34 | new Column\Boolean('featured', null, false, 0), 35 | new Column\Boolean('sticky', null, false, 0) 36 | ]; 37 | 38 | $this->constraints = [ 39 | new \Zend\Db\Sql\Ddl\Constraint\PrimaryKey('id'), 40 | new \Zend\Db\Sql\Ddl\Constraint\UniqueKey('slug'), 41 | new \Zend\Db\Sql\Ddl\Index\Index('pub_date'), 42 | new Constraint\Fulltext(['slug', 'title'], 'title_fts'), 43 | new Constraint\Fulltext(['content'], 'content'), 44 | new Constraint\Fulltext(['slug', 'title', 'content'], 'all_fts') 45 | ]; 46 | } 47 | 48 | public function setTable($t){ 49 | return $this; 50 | } 51 | 52 | public function getTable(){ 53 | return $this->table; 54 | } 55 | } -------------------------------------------------------------------------------- /app/source/styles/users.scss: -------------------------------------------------------------------------------- 1 | @import "partials/variables"; 2 | $user-list-item-image-size: 8rem; 3 | 4 | .user-list { 5 | height: 100%; 6 | width: 100%; 7 | padding: 2rem 1rem; 8 | user-select: none; 9 | display: flex !important; // Needed set to important because our search changes display to "block" 10 | } 11 | 12 | .user-list-none { 13 | text-align: center; 14 | font-size: 1.3rem; 15 | color: $text-muted; 16 | padding: 1rem; 17 | } 18 | 19 | .user-list-item { 20 | height: $user-list-item-image-size + 6rem; 21 | display: block; 22 | position: relative; 23 | text-align: center; 24 | border-radius: $border-radius-large; 25 | border: solid 2px white; 26 | padding: 1rem; 27 | padding-top: $user-list-item-image-size + 1.75rem; 28 | transition: 29 | color $leafpub-transition-speed, 30 | background-color $leafpub-transition-speed; 31 | .avatar { 32 | position: absolute; 33 | top: 1rem; 34 | left: calc(50% - #{$user-list-item-image-size} / 2); 35 | width: $user-list-item-image-size; 36 | height: $user-list-item-image-size; 37 | overflow: hidden; 38 | img, 39 | i { 40 | background: $gray-100; 41 | width: $user-list-item-image-size; 42 | height: $user-list-item-image-size; 43 | line-height: $user-list-item-image-size; 44 | font-size: $user-list-item-image-size - 2rem; 45 | color: white; 46 | border-radius: 50%; 47 | } 48 | } 49 | .name { 50 | font-weight: bold; 51 | @include text-truncate; 52 | } 53 | .role { 54 | font-size: .9rem; 55 | color: $text-muted; 56 | @include text-truncate; 57 | transition: color $leafpub-transition-speed; 58 | } 59 | &:hover { 60 | background-color: #f2f2f2; 61 | } 62 | &:hover, 63 | &:focus { 64 | text-decoration: none; 65 | } 66 | &.selected { 67 | background-color: $leafpub-blue; 68 | color: white; 69 | .role { 70 | color: white; 71 | } 72 | } 73 | } 74 | 75 | // Fix sticky hovers in iOS 76 | .ios .user-list-item:hover:not(.selected) { 77 | background-color: inherit; 78 | } 79 | 80 | // XS breakpoint 81 | @include media-breakpoint-down(xs) { 82 | .user-list { 83 | padding: 1rem 0; 84 | } 85 | } -------------------------------------------------------------------------------- /app/source/templates/partials/plugin-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each plugins}} 2 |
6 |
7 | {{#if image}} 8 | 9 | {{else}} 10 | {{#if isWidget}} 11 | 12 | {{else}} 13 | 14 | {{/if}} 15 | {{/if}} 16 |
17 |
18 |

19 | {{name}} 20 |

21 |
22 |
23 | {{description}} 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {{#is isAdminPlugin}} 41 | 42 | 43 | 44 | {{/is}} 45 | {{#is isMiddleware}} 46 | 47 | 48 | 49 | {{/is}} 50 | 51 | 52 | 53 |
54 |
55 | {{else}} 56 |
57 |
58 | {{L 'no_plugins'}} 59 |
60 |
61 | {{/each}} -------------------------------------------------------------------------------- /app/source/templates/partials/history-table.hbs: -------------------------------------------------------------------------------- 1 | {{#if history}} 2 | 3 | {{#each history}} 4 | 5 | 23 | 33 | 43 | 53 | 54 | {{/each}} 55 |
6 | {{date rev_date format="%d %b %Y @ %H:%M"}} 7 |
8 | 9 | {{#admin_author post_data.author}} 10 | {{#if avatar}} 11 | 12 | {{/if}} 13 | {{name}} 14 | {{else}} 15 | {{post_data.author}} 16 | {{/admin_author}} 17 | 18 | {{#if initial}} 19 | · {{L 'initial_version'}} 20 | {{/if}} 21 | 22 |
24 | 34 | 42 | 44 | 51 | 52 |
56 | {{else}} 57 |
58 |
59 | 60 | {{L 'no_history'}} 61 |
62 |
63 | {{/if}} -------------------------------------------------------------------------------- /app/source/scripts/login.js: -------------------------------------------------------------------------------- 1 | /* globals Nanobar, Leafpub */ 2 | $(function() { 3 | 'use strict'; 4 | 5 | var progress = new Nanobar(), 6 | redirect = $('.login-form').attr('data-redirect'); 7 | 8 | // Tooltip 9 | $('[data-toggle="tooltip"]').tooltip({ 10 | trigger: 'hover' 11 | }); 12 | 13 | // Submit 14 | $('.login-form').on('submit', function(event) { 15 | var form = this, 16 | url; 17 | 18 | event.preventDefault(); 19 | 20 | // Determine URL 21 | switch(Leafpub.template) { 22 | case 'login.recover': 23 | url = Leafpub.url('api/login/recover'); 24 | break; 25 | case 'login.reset': 26 | url = Leafpub.url('api/login/reset'); 27 | break; 28 | default: 29 | url = Leafpub.url('api/login'); 30 | break; 31 | } 32 | 33 | // Show progress 34 | progress.go(50); 35 | 36 | // Hide errors 37 | Leafpub.highlightErrors(form); 38 | $('.form-message').prop('hidden', true); 39 | 40 | // Send request 41 | $.ajax({ 42 | url: url, 43 | type: 'POST', 44 | data: $(form).serialize() 45 | }) 46 | .done(function(res) { 47 | // Show message 48 | if(res.message) { 49 | $('.form-message') 50 | .toggleClass('text-success', res.success) 51 | .toggleClass('text-warning', !res.success) 52 | .text(res.message) 53 | .prop('hidden', false); 54 | } 55 | 56 | if(res.success) { 57 | // Disable the form and do nothing if we're recovering 58 | if(Leafpub.template === 'login.recover') { 59 | $(form).find(':input').prop('disabled', true); 60 | return; 61 | } 62 | 63 | // Redirect to a custom URL or to the main admin page 64 | location.href = redirect ? redirect : Leafpub.adminUrl(); 65 | } else { 66 | // Show errors 67 | Leafpub.highlightErrors(form, res.invalid); 68 | 69 | // Shake on error 70 | $('.login-form') 71 | .velocity('stop', true) 72 | .velocity('callout.shake'); 73 | } 74 | }) 75 | .always(function() { 76 | // Hide progress 77 | progress.go(100); 78 | }); 79 | }); 80 | }); -------------------------------------------------------------------------------- /app/source/templates/partials/post-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each posts}} 2 | 8 | {{#is sticky}} 9 | {{L 'sticky'}} 10 | {{/is}} 11 | {{#date_compare pub_date '>' 'now'}} 12 | {{L 'scheduled'}} 13 | {{/date_compare}} 14 | {{#is page}} 15 | {{L 'page'}} 16 | {{/is}} 17 | {{#is featured}} 18 | {{L 'featured'}} 19 | {{/is}} 20 | {{#is status 'draft'}} 21 | {{L 'draft'}} 22 | {{/is}} 23 |
24 | {{title}} 25 |
26 |
27 | {{#if meta.lock}} 28 | {{#admin_author meta.lock.0}} 29 | {{#is ../meta.lock.0 "===" @user.slug}} 30 | {{#if avatar}} 31 | 32 | {{else}} 33 | 34 | 35 | 36 | {{/if}} 37 | {{else}} 38 | 39 | 40 | 41 | {{/is}} 42 | {{/admin_author}} 43 | {{else}} 44 | {{#admin_author author}} 45 | {{#if avatar}} 46 | 47 | {{else}} 48 | 49 | 50 | 51 | {{/if}} 52 | {{/admin_author}} 53 | {{/if}} 54 | 55 | {{date format="%B %e, %Y"}} 56 | 57 |
58 |
59 | {{else}} 60 |
61 |
62 | {{L 'no_posts'}} 63 |
64 |
65 | {{/each}} -------------------------------------------------------------------------------- /app/source/classes/Error.php: -------------------------------------------------------------------------------- 1 | Theme::getErrorTemplate($code), 34 | 'data' => $data, 35 | 'special_vars' => array_merge([ 36 | 'meta' => [ 37 | 'title' => Language::term('not_found'), 38 | 'description' => '' 39 | ] 40 | ], $special), 41 | 'helpers' => ['url', 'utility', 'theme'] 42 | ]); 43 | } 44 | 45 | /** 46 | * Renders the system error template and exits. This method should be called before any output 47 | * is sent to the browser and only when an irrecoverable error occurs. 48 | * 49 | * Important: This method should not use the handlebars engine, database resources, or any 50 | * assets over HTTP because it may be triggered any time, even before a database connection is 51 | * established. 52 | * 53 | * @param array $data 54 | * @return mixed 55 | * 56 | **/ 57 | public static function system($data) { 58 | // Get the template 59 | $template = self::path('source/templates/error.system.hbs'); 60 | $html = file_get_contents($template); 61 | 62 | // Convert image to data URI 63 | $image = 'data:image/png;base64,' . base64_encode( 64 | file_get_contents(self::path('source/assets/img/logo-color.png')) 65 | ); 66 | 67 | // Update placeholders 68 | $html = str_replace('{{logo}}', htmlspecialchars($image), $html); 69 | $html = str_replace('{{title}}', htmlspecialchars($data['title']), $html); 70 | $html = str_replace('{{message}}', htmlspecialchars($data['message']), $html); 71 | $html = str_replace('{{host}}', htmlspecialchars($_SERVER['HTTP_HOST']), $html); 72 | 73 | return $html; 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /app/source/classes/Theme.php: -------------------------------------------------------------------------------- 1 | isDot() && $dir->isDir()) { 39 | // Attempt to read and decode theme.json 40 | $theme_json = $dir->getPathname() . '/theme.json'; 41 | $json = json_decode(file_get_contents($theme_json), true); 42 | if(!$json) continue; 43 | // Add it 44 | $themes[] = array_merge($json, ['dir' => $dir->getFilename()]); 45 | } 46 | } 47 | 48 | return $themes; 49 | } 50 | 51 | /** 52 | * Returns the path to the current theme, optionally concatenating a path 53 | * 54 | * @return String 55 | * 56 | **/ 57 | public static function getPath() { 58 | $paths = func_get_args(); 59 | $base_path = 'content/themes/' . Setting::getOne('theme'); 60 | 61 | return self::path($base_path, implode('/', $paths)); 62 | } 63 | 64 | public static function getErrorTemplate($code){ 65 | if (!self::$_themeOptions){ 66 | self::$_themeOptions = json_decode( 67 | file_get_contents( 68 | self::path('content/themes/' . Setting::getOne('theme') . '/theme.json') 69 | , true 70 | ) 71 | ); 72 | } 73 | $file = self::getPath(self::$_themeOptions->error_templates->{$code}); 74 | if (!is_file($file)){ 75 | if ($code == '503'){ 76 | $file = self::path(self::THEME_STD_MAINTENANCE); 77 | } else { 78 | $file = self::getPath(self::THEME_STD_ERROR); 79 | } 80 | } 81 | return $file; 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /app/source/styles/plugins.scss: -------------------------------------------------------------------------------- 1 | @import "partials/variables"; 2 | $plugin-list-item-image-size: 8rem; 3 | 4 | .upload-new-plugin { 5 | margin-bottom: 0px; 6 | } 7 | 8 | 9 | .plugin-list { 10 | height: 100%; 11 | padding: 2rem 1rem; 12 | user-select: none; 13 | } 14 | 15 | .plugin-list-none { 16 | text-align: center; 17 | font-size: 1.3rem; 18 | color: $text-muted; 19 | padding: 1rem; 20 | } 21 | 22 | .plugin-list-item { 23 | height: $plugin-list-item-image-size + 9rem; 24 | position: relative; 25 | text-align: center; 26 | border-radius: $border-radius-large; 27 | border: solid 2px white; 28 | padding: 1rem; 29 | padding-top: $plugin-list-item-image-size + 1.75rem; 30 | transition: 31 | color $leafpub-transition-speed, 32 | background-color $leafpub-transition-speed; 33 | .preview { 34 | position: absolute; 35 | top: 1rem; 36 | left: calc(50% - #{$plugin-list-item-image-size} / 2); 37 | width: $plugin-list-item-image-size; 38 | height: $plugin-list-item-image-size; 39 | overflow: hidden; 40 | img, 41 | i { 42 | background: $gray-100; 43 | width: $plugin-list-item-image-size; 44 | height: $plugin-list-item-image-size; 45 | line-height: $plugin-list-item-image-size; 46 | font-size: $plugin-list-item-image-size - 2rem; 47 | color: white; 48 | border-radius: 50%; 49 | } 50 | } 51 | .name h4{ 52 | font-weight: bold; 53 | @include text-truncate; 54 | color: #09d; 55 | } 56 | .info, .description { 57 | font-size: .9rem; 58 | color: $text-muted; 59 | @include text-truncate; 60 | transition: color $leafpub-transition-speed; 61 | padding: 5px; 62 | .badge.enable{ 63 | visibility: hidden; 64 | } 65 | } 66 | &:hover { 67 | background-color: #f2f2f2; 68 | } 69 | &:hover, 70 | &:focus { 71 | text-decoration: none; 72 | } 73 | &.selected { 74 | background-color: $leafpub-blue; 75 | color: white; 76 | .info, .description, .name h4 { 77 | color: white; 78 | } 79 | } 80 | .info a { 81 | color: white; 82 | } 83 | .info .badge { 84 | font-size: .9rem; 85 | } 86 | } 87 | 88 | .plugin-list-item.enabled { 89 | .info .badge.enable { 90 | visibility: visible; 91 | } 92 | } 93 | // Fix sticky hovers in iOS 94 | .ios .plugin-list-item:hover:not(.selected) { 95 | background-color: inherit; 96 | } 97 | 98 | // XS breakpoint 99 | @include media-breakpoint-down(xs) { 100 | .plugin-list { 101 | padding: 1rem 0; 102 | } 103 | } -------------------------------------------------------------------------------- /app/source/styles/partials/_editor-toolbar.scss: -------------------------------------------------------------------------------- 1 | // Editor toolbar 2 | .editor-toolbar { 3 | position: relative; 4 | width: 100%; 5 | background: white; 6 | border-bottom: solid 1px #eee; 7 | padding: .25rem .25rem; 8 | user-select: none; 9 | // Clearfix for floated btn-group 10 | &::after { 11 | content: ' '; 12 | display: table; 13 | clear: both; 14 | } 15 | // Float the save group to keep it on the right 16 | .btn-group:last-child { 17 | float: right; 18 | margin-right: 1rem; 19 | } 20 | .btn-group { 21 | margin: .25rem; 22 | } 23 | .btn { 24 | i { 25 | width: 1em; 26 | transform: scale(1); 27 | } 28 | &.on { 29 | color: $leafpub-blue; 30 | } 31 | 32 | &.btn-light { 33 | &:hover { 34 | background: $leafpub-blue; 35 | color: #fff; 36 | } 37 | } 38 | } 39 | 40 | // Dropdown buttons 41 | .dropdown-btn { 42 | @extend .btn; 43 | @extend .btn-light; 44 | position: relative; 45 | padding: 0; 46 | display: inline-block; 47 | .dropdown-toggle { 48 | @include button-size($btn-padding-y, $btn-padding-x, $font-size-root, 1rem, $border-radius); 49 | position: relative; 50 | padding-right: $btn-padding-x * .75; 51 | display: block; 52 | } 53 | i { 54 | width: .8em; 55 | } 56 | } 57 | .btn-group .dropdown-btn { 58 | float: left; 59 | } 60 | 61 | // Dropdown menus 62 | .dropdown-menu { 63 | left: -3.5rem; 64 | .dropdown-item { 65 | i { 66 | width: 1.2em; 67 | color: $dark; 68 | margin-right: .5rem; 69 | } 70 | // On state 71 | &.on, 72 | &.on i { 73 | color: $leafpub-blue; 74 | } 75 | // Hover state supercedes on state 76 | &:hover, 77 | &:hover i { 78 | color: $dropdown-link-hover-color; 79 | } 80 | } 81 | } 82 | 83 | // Formatters 84 | [data-editor="heading1"] { font-size: 1.3rem; line-height: 1.4; } 85 | [data-editor="heading2"] { font-size: 1.2rem; line-height: 1.4; } 86 | [data-editor="heading3"] { font-size: 1.1rem; line-height: 1.4; } 87 | [data-editor="heading4"] { font-size: 1rem; line-height: 1.4; } 88 | [data-editor="heading5"] { font-size: .9rem; line-height: 1.4; } 89 | [data-editor="heading6"] { font-size: .8rem; line-height: 1.4; } 90 | [data-editor="preformatted"] { font-family: $font-family-monospace; } 91 | [data-editor="blockquote"] { font-style: italic; } 92 | } 93 | 94 | // XS breakpoint 95 | @include media-breakpoint-down(xs) { 96 | .editor-toolbar .btn { 97 | padding: .25rem .5rem; 98 | } 99 | 100 | .editor-toolbar .dropdown-btn .dropdown-toggle { 101 | padding: 0; 102 | } 103 | } -------------------------------------------------------------------------------- /app/source/styles/partials/_overrides.scss: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////////////////////////// 2 | // Transitions 3 | //////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | // Make sure transitions don't fire before the page is loaded 6 | .preload * { 7 | transition: none !important; 8 | } 9 | 10 | //////////////////////////////////////////////////////////////////////////////////////////////////// 11 | // Anchors 12 | //////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | a:focus { 15 | outline: none; 16 | } 17 | 18 | //////////////////////////////////////////////////////////////////////////////////////////////////// 19 | // Forms 20 | //////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | form label { 23 | color: #999; 24 | } 25 | 26 | .form-control, 27 | form label, 28 | .form-control .inner-addon { 29 | transition: 30 | border-color $leafpub-transition-speed, 31 | color $leafpub-transition-speed; 32 | } 33 | 34 | .has-danger label { 35 | color: $brand-danger; 36 | } 37 | 38 | .has-success label { 39 | color: $brand-success; 40 | } 41 | 42 | .has-warning label { 43 | color: $brand-warning; 44 | } 45 | 46 | // Remove shadows from iOS inputs 47 | input[type="date"], 48 | input[type="email"], 49 | input[type="number"], 50 | input[type="password"], 51 | input[type="text"], 52 | input[type="time"], 53 | input[type="url"], 54 | textarea { 55 | -webkit-appearance: none; 56 | } 57 | 58 | // Checkboxes & radios 59 | .checkbox, 60 | .radio { 61 | input[type="checkbox"], 62 | input[type="radio"] { 63 | font-size: 1.2em; 64 | margin-right: .2em; 65 | } 66 | } 67 | 68 | // Inputs containing code 69 | input.code, 70 | textarea.code { 71 | font-family: $font-family-monospace; 72 | font-weight: 300; 73 | } 74 | 75 | // Use custom form styles for select elements 76 | select.form-control:not([size]):not([multiple]) { 77 | @extend .custom-select; 78 | height: 2.35rem; 79 | } 80 | 81 | .btn { 82 | transition: 83 | background-color $leafpub-transition-speed, 84 | border-color $leafpub-transition-speed, 85 | color $leafpub-transition-speed; 86 | // Remove focus outline 87 | &:focus, 88 | &.focus, 89 | &:active:focus, 90 | &:active.focus, 91 | &.active:focus, 92 | &.active.focus { 93 | outline: none; 94 | } 95 | } 96 | 97 | // Black/white buttons at 50% opacity 98 | .btn-translucent-dark { 99 | @include button-variant( rgba(black, .5), transparent); 100 | &:hover { 101 | background-color: rgba(black, .6); 102 | } 103 | } 104 | 105 | //////////////////////////////////////////////////////////////////////////////////////////////////// 106 | // Dropdowns 107 | //////////////////////////////////////////////////////////////////////////////////////////////////// 108 | 109 | .dropdown-item { 110 | padding-top: 5px; 111 | padding-bottom: 5px; 112 | } -------------------------------------------------------------------------------- /app/source/scripts/modules/reauthenticate.js: -------------------------------------------------------------------------------- 1 | // Allow users to reauthenticate on-page when an AJAX request fails with a 401 HTTP error 2 | $(function() { 3 | 'use strict'; 4 | 5 | // Hook into AJAX errors to check for 401 Unauthorized 6 | $.ajaxPrefilter(function(options, originalOptions, jqXHR) { 7 | var defer = $.Deferred(); 8 | 9 | // Resolve successful requests normally 10 | jqXHR.done(defer.resolve); 11 | 12 | // Hook into failed requests to check for 401 Unauthorized 13 | jqXHR.fail(function() { 14 | var args = Array.prototype.slice.call(arguments); 15 | 16 | function reject() { 17 | defer.rejectWith(jqXHR, args); 18 | } 19 | 20 | function loginPrompt() { 21 | var json = jqXHR.responseJSON, 22 | message = json.message, 23 | username = json.language.username, 24 | password = json.language.password; 25 | 26 | // Prompt for username/password 27 | $.alertable.prompt(message, { 28 | prompt: 29 | '
' + 30 | '' + 33 | '' + 34 | '
' + 35 | '
' + 36 | '' + 39 | '' + 40 | '
' 41 | }).then(function(data) { 42 | // Attempt login 43 | $.ajax({ 44 | url: Leafpub.url('api/login'), 45 | type: 'POST', 46 | data: { 47 | username: data.username, 48 | password: data.password 49 | } 50 | }) 51 | .done(function(res) { 52 | if(res.success) { 53 | // Resubmit original request and pass along resolve/reject 54 | $.ajax(originalOptions).then(defer.resolve, defer.reject); 55 | } else { 56 | // Show error message 57 | $.alertable.alert(res.message).then(loginPrompt, reject); 58 | } 59 | }); 60 | }, function() { 61 | reject(); 62 | }); 63 | } 64 | 65 | // Unauthorized? 66 | if(jqXHR.status === 401) { 67 | // Show login prompt 68 | loginPrompt(); 69 | } else { 70 | reject(); 71 | } 72 | }); 73 | 74 | return defer.promise(jqXHR); 75 | }); 76 | }); -------------------------------------------------------------------------------- /app/source/styles/partials/_input-inner-addon.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Inner Input Addons - allows input addons inside text controls 4 | 5 | Addon on left: 6 | 7 |
8 | 9 | 10 |
11 | 12 | Addon on right: 13 | 14 |
15 | 16 | 17 |
18 | 19 | Addons on left and right: 20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 | Tips: 28 | 29 | - Works with form-control-sm and form-control-lg controls 30 | - Works with loaders 31 | 32 | */ 33 | 34 | .inner-addon-group { 35 | position: relative; 36 | 37 | // Form controls 38 | .form-control:not(:first-child) { 39 | padding-left: 34px; 40 | } 41 | .form-control:not(:last-child) { 42 | padding-right: 34px; 43 | } 44 | 45 | // Small form controls 46 | .form-control-sm:not(:first-child) { 47 | padding-left: 28px; 48 | } 49 | .form-control-sm:not(:last-child) { 50 | padding-right: 28px; 51 | } 52 | 53 | // Large form controls 54 | .form-control-lg:not(:first-child) { 55 | padding-left: 40px; 56 | } 57 | .form-control-lg:not(:last-child) { 58 | padding-right: 40px; 59 | } 60 | 61 | // Addons 62 | .inner-addon { 63 | color: lighten($input-color, 30%); 64 | font-size: 1em; 65 | line-height: 2em; 66 | position: absolute; 67 | top: 0; 68 | margin: 2px 12px; 69 | } 70 | .inner-addon:first-child { 71 | left: 0; 72 | } 73 | .inner-addon:last-child { 74 | right: 0; 75 | } 76 | 77 | // Links/file inputs 78 | label.inner-addon, 79 | a.inner-addon i { 80 | color: $link-color; 81 | text-decoration: none; 82 | cursor: pointer; 83 | transition: $leafpub-transition-speed color; 84 | &:hover { 85 | color: $link-hover-color; 86 | } 87 | } 88 | 89 | // Small addons 90 | .inner-addon-sm { 91 | font-size: .8em; 92 | margin: 3px 10px; 93 | } 94 | 95 | // Large addons 96 | .inner-addon-lg { 97 | font-size: 1.2em; 98 | margin: 5px 15px; 99 | } 100 | 101 | // Link addons 102 | a.inner-addon { 103 | color: $text-muted; 104 | text-decoration: none; 105 | } 106 | } 107 | 108 | // Modifiers 109 | .has-warning .inner-addon-group .inner-addon { 110 | color: $brand-warning; 111 | } 112 | 113 | .has-danger .inner-addon-group .inner-addon { 114 | color: $brand-danger; 115 | } 116 | .has-success .inner-addon-group .inner-addon { 117 | color: $brand-success; 118 | } -------------------------------------------------------------------------------- /app/source/classes/Models/PostMeta.php: -------------------------------------------------------------------------------- 1 | select(['post' => $slug])->toArray(); 40 | return $data; 41 | } catch(\Exception $e){ 42 | 43 | } 44 | } 45 | 46 | public static function getOne($data){ 47 | $slug = $data['post']; 48 | if (!$slug){ 49 | throw new \Exception('No post present!'); 50 | } 51 | $name = $data['name']; 52 | if (!$name){ 53 | throw new \Exception('No meta name present!'); 54 | } 55 | 56 | try { 57 | $data = self::getModel()->select(['post' => $slug, 'name' => $name]); 58 | if ($data){ 59 | return $data->current(); 60 | } 61 | } catch(\Exception $e){ 62 | 63 | } 64 | } 65 | 66 | public static function create($data){ 67 | if (!isset($data['post'])){ 68 | throw new \Exception('No post present!'); 69 | } 70 | 71 | try { 72 | return self::getModel()->insert($data); 73 | } catch(\Exception $e){ 74 | exit($e->getMessage()); 75 | } 76 | } 77 | 78 | public static function edit($data){ 79 | $slug = $data['post']; 80 | if (!$slug){ 81 | throw new \Exception('No post present!'); 82 | } 83 | unset($data['slug']); 84 | try { 85 | return self::getModel()->update($data, ['post' => $slug]); 86 | } catch(\Exception $e){ 87 | 88 | } 89 | } 90 | 91 | public static function delete($data){ 92 | $post = $data['post']; 93 | $name = $data['name']; 94 | 95 | if (!$post){ 96 | throw new \Exception('No post present!'); 97 | } 98 | 99 | if (!$name){ 100 | throw new \Exception('No post present!'); 101 | } 102 | 103 | try { 104 | return self::getModel()->delete(['post' => $post, 'name' => $name]); 105 | } catch(\Exception $e){ 106 | 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /app/source/templates/helpers/admin.php: -------------------------------------------------------------------------------- 1 | function($slug, $options = null) { 8 | $author = \Leafpub\Models\User::getOne($slug); 9 | 10 | // Do {{else}} if no author is found 11 | if(!$author) { 12 | return $options['inverse'] ? $options['inverse']() : ''; 13 | } 14 | 15 | // Remove sensitive data 16 | unset($author['password'], $author['reset_token']); 17 | 18 | return $options['fn']($author); 19 | }, 20 | 21 | // Gets all admin menu items 22 | 'admin_menu' => function() { 23 | $args = func_get_args(); 24 | $options = end($args); 25 | $items = \Leafpub\Admin::getMenuItems(); 26 | 27 | // Generate `current` value for each item 28 | foreach($items as $key => $value) { 29 | $items[$key]['current'] = \Leafpub\Leafpub::isCurrentUrl($value['link']); 30 | } 31 | 32 | if(count($items)) { 33 | return $options['fn']([ 34 | 'items' => $items 35 | ]); 36 | } else { 37 | // No items, do {{else}} 38 | return $options['inverse'] ? $options['inverse']() : ''; 39 | } 40 | }, 41 | 42 | // Outputs admin scripts 43 | 'admin_scripts' => function() { 44 | $args = func_get_args(); 45 | $options = end($args); 46 | $html = ''; 47 | 48 | foreach((array) $options['_this']['scripts'] as $script) { 49 | // If this is a fully qualified URL, return is as-is 50 | if(preg_match('/^(http:|https:|mailto:|\/\/:)/i', $script)) { 51 | $src = $script; 52 | } else { 53 | $src = 54 | self::url('source/assets/js', $script) . '?v=' . 55 | $options['data']['leafpub']['version']; 56 | } 57 | $html .= ''; 58 | } 59 | 60 | if (isset($options['_this']['plugin_scripts'])){ 61 | foreach((array) $options['_this']['plugin_scripts'] as $name => $script) { 62 | $src = 63 | self::url('content/plugins/' . $name, $script) . '?v=' . 64 | $options['data']['leafpub']['version']; 65 | $html .= ''; 66 | } 67 | } 68 | 69 | return new \LightnCandy\SafeString($html); 70 | }, 71 | 72 | // Outputs admin styles 73 | 'admin_styles' => function() { 74 | $args = func_get_args(); 75 | $options = end($args); 76 | $html = ''; 77 | 78 | foreach((array) $options['_this']['styles'] as $style) { 79 | $href = 80 | self::url('source/assets/css', $style) . '?v=' . 81 | $options['data']['leafpub']['version']; 82 | $html .= ''; 83 | } 84 | 85 | return new \LightnCandy\SafeString($html); 86 | }, 87 | 88 | 'widget' => function($options){ 89 | $widget = $content = $options['_this']['widget']; 90 | return new \LightnCandy\SafeString($widget); 91 | }, 92 | ]; -------------------------------------------------------------------------------- /app/source/templates/navigation.hbs: -------------------------------------------------------------------------------- 1 | {{> header}} 2 | 3 | {{> menu}} 4 | 5 | {{!-- Toolbar --}} 6 |
7 |
8 | {{!-- Left side --}} 9 |
10 |

11 | {{L 'navigation'}} 12 |

13 |
14 | {{!-- Right side --}} 15 |
16 | 19 |
20 |
21 |
22 | 23 | {{!-- Main container --}} 24 |
25 | {{!-- Nav items --}} 26 | 35 | 36 | {{!-- Template for nav items --}} 37 | 40 | 41 | {{!-- Add --}} 42 |
43 | 44 | 47 | 75 | 78 | 83 |
84 |
85 | 86 | {{> footer}} --------------------------------------------------------------------------------