Commit c0e66d5f authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat: enhance ui using javascript in admin area

- bundle js using parcel
- add markdown editor, html editor, dropdown and tooltip features using third-party packages
- integrate optimized inline svg icons from RemixIcon using svgo and a php helper
- add scripts in package.json to bundle icons, images, css and js
- update tailwind config to add purgecss lookups and typography plugin
- refactor views to add missing pages in user journey
- update admin's holy grail layout using css grid
parent d58e5187
......@@ -15,13 +15,15 @@
"color-highlight.markerType": "dot-before"
},
"extensions": [
"mikestead.dotenv",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind",
"wayou.vscode-todo-highlight",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss"
]
"mikestead.dotenv",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind",
"wayou.vscode-todo-highlight",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"jamesbirtles.svelte-vscode",
"dbaeumer.vscode-eslint"
]
}
{
"env": {
"browser": true,
"es2020": true
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {}
}
......@@ -129,6 +129,9 @@ nb-configuration.xml
yarn.lock
node_modules
# JS
.cache
# public folder
public/*
!public/.htaccess
......
plugins:
- removeXMLNS: true
- removeDimensions: true
- addAttributesToSVGElement:
attributes:
- fill: currentColor
- width: "1em"
- height: "1em"
- sortAttrs: true
plugins:
- removeXMLNS: true
- removeDimensions: true
- addAttributesToSVGElement:
attributes:
- width: "1em"
- height: "1em"
- sortAttrs: true
......@@ -2,15 +2,23 @@
Castopod uses the following components:
PHP Dependencies:
- [Code Igniter 4](https://codeigniter.com) ([MIT License](https://codeigniter.com/user_guide/license.html))
- [tailwindcss](https://tailwindcss.com/) ([MIT License](https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE))
- [Tatter\Relations](https://github.com/tattersoftware/codeigniter4-relations) ([MIT License](https://github.com/tattersoftware/codeigniter4-relations/blob/develop/LICENSE))
- [D3: Data-Driven Documents](https://github.com/d3/d3) ([BSD 3-Clause "New" or "Revised" License](https://github.com/d3/d3/blob/master/LICENSE))
- [Rollup](https://github.com/rollup/rollup) ([MIT license](https://github.com/rollup/rollup/blob/master/LICENSE.md))
- [Svelte](https://github.com/sveltejs/svelte) ([MIT license](https://github.com/sveltejs/svelte/blob/master/LICENSE))
- [User agent list](https://github.com/opawg/user-agents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE))
- [WhichBrowser/Parser-PHP](https://github.com/WhichBrowser/Parser-PHP) ([MIT License](https://github.com/WhichBrowser/Parser-PHP/blob/master/LICENSE))
- [GeoIP2 PHP API](https://github.com/maxmind/GeoIP2-php) ([Apache License 2.0](https://github.com/maxmind/GeoIP2-php/blob/master/LICENSE))
- [Quill Rich Text Editor](https://github.com/quilljs/quill) ([BSD 3-Clause "New" or "Revised" License](https://github.com/quilljs/quill/blob/develop/LICENSE))
- [getID3](https://github.com/JamesHeinrich/getID3) ([GNU General Public License v3](https://github.com/JamesHeinrich/getID3/blob/2.0/licenses/license.gpl-30.txt))
- [myth-auth](https://github.com/lonnieezell/myth-auth) ([MIT license](https://github.com/lonnieezell/myth-auth/blob/develop/LICENSE.md))
- [parsedown](https://github.com/erusev/parsedown) ([MIT license](https://github.com/erusev/parsedown/blob/master/LICENSE.txt))
Javascript dependencies:
- [tailwindcss](https://tailwindcss.com/) ([MIT License](https://github.com/tailwindcss/tailwindcss/blob/master/LICENSE))
- [CodeMirror](https://github.com/codemirror/CodeMirror) ([MIT License](https://github.com/codemirror/CodeMirror/blob/master/LICENSE))
- [ProseMirror](https://prosemirror.net/) ([MIT License](https://github.com/ProseMirror/prosemirror/blob/master/LICENSE))
- [D3: Data-Driven Documents](https://github.com/d3/d3) ([BSD 3-Clause "New" or "Revised" License](https://github.com/d3/d3/blob/master/LICENSE))
Other:
- [RemixIcon](https://remixicon.com/) ([Apache License 2.0](https://github.com/Remix-Design/RemixIcon/blob/master/License))
......@@ -61,12 +61,9 @@ $routes->group(
['namespace' => 'App\Controllers\Admin'],
function ($routes) {
$routes->get('/', 'Home', [
'as' => 'admin',
'as' => 'admin_home',
]);
$routes->get('my-podcasts', 'Podcast::myPodcasts', [
'as' => 'my_podcasts',
]);
$routes->get('podcasts', 'Podcast::list', [
'as' => 'podcast_list',
'filter' => 'permission:podcasts-list',
......@@ -81,6 +78,9 @@ $routes->group(
// Use ids in admin area to help permission and group lookups
$routes->group('podcasts/(:num)', function ($routes) {
$routes->get('/', 'Podcast::view/$1', [
'as' => 'podcast_view',
]);
$routes->get('edit', 'Podcast::edit/$1', [
'as' => 'podcast_edit',
]);
......@@ -98,6 +98,9 @@ $routes->group(
]);
$routes->post('new-episode', 'Episode::attemptCreate/$1');
$routes->get('episodes/(:num)', 'Episode::view/$1/$2', [
'as' => 'episode_view',
]);
$routes->get('episodes/(:num)/edit', 'Episode::edit/$1/$2', [
'as' => 'episode_edit',
]);
......
......@@ -77,6 +77,13 @@ class Episode extends BaseController
return view('admin/episode/list', $data);
}
public function view()
{
$data = ['episode' => $this->episode];
return view('admin/episode/view', $data);
}
public function create()
{
helper(['form']);
......
......@@ -18,33 +18,28 @@ class Podcast extends BaseController
{
if (count($params) > 0) {
switch ($method) {
case 'edit':
case 'view':
if (
!has_permission('podcasts-edit') ||
!has_permission("podcasts:$params[0]-edit")
!has_permission('podcasts-view') ||
!has_permission("podcasts:$params[0]-view")
) {
throw new \RuntimeException(
lang('Auth.notEnoughPrivilege')
);
}
case 'delete':
case 'edit':
if (
!has_permission('podcasts-delete') ||
!has_permission("podcasts:$params[0]-delete")
!has_permission('podcasts-edit') ||
!has_permission("podcasts:$params[0]-edit")
) {
throw new \RuntimeException(
lang('Auth.notEnoughPrivilege')
);
}
case 'listContributors':
case 'addContributor':
case 'editContributor':
case 'deleteContributor':
case 'delete':
if (
!has_permission('podcasts-manage_contributors') ||
!has_permission(
"podcasts:$params[0]-manage_contributors"
)
!has_permission('podcasts-delete') ||
!has_permission("podcasts:$params[0]-delete")
) {
throw new \RuntimeException(
lang('Auth.notEnoughPrivilege')
......@@ -61,22 +56,27 @@ class Podcast extends BaseController
return $this->$method();
}
public function myPodcasts()
public function list()
{
$data = [
'all_podcasts' => (new PodcastModel())->getUserPodcasts(user()->id),
];
$podcast_model = new PodcastModel();
$all_podcasts = [];
if (has_permission('podcasts-list')) {
$all_podcasts = $podcast_model->findAll();
} else {
$all_podcasts = $podcast_model->getUserPodcasts(user()->id);
}
$data = ['all_podcasts' => $all_podcasts];
return view('admin/podcast/list', $data);
}
public function list()
public function view()
{
$podcast_model = new PodcastModel();
$data = ['podcast' => $this->podcast];
$data = ['all_podcasts' => $podcast_model->findAll()];
return view('admin/podcast/list', $data);
return view('admin/podcast/view', $data);
}
public function create()
......
......@@ -65,6 +65,7 @@ class AuthSeeder extends Seeder
'name' => 'list',
'description' => 'List all podcasts and their episodes',
],
['name' => 'view', 'description' => 'View any podcast'],
['name' => 'edit', 'description' => 'Edit any podcast'],
[
'name' => 'manage_contributors',
......
......@@ -9,6 +9,8 @@ namespace App\Entities;
use App\Models\PodcastModel;
use CodeIgniter\Entity;
use League\CommonMark\CommonMarkConverter;
use Parsedown;
class Episode extends Entity
{
......@@ -22,6 +24,7 @@ class Episode extends Entity
protected string $enclosure_media_path;
protected string $enclosure_url;
protected array $enclosure_metadata;
protected string $description_html;
protected $casts = [
'slug' => 'string',
......@@ -153,4 +156,26 @@ class Episode extends Entity
return $podcast_model->find($this->attributes['podcast_id']);
}
public function getDescriptionHtml()
{
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
'renderer' => [
'soft_break' => '<br>',
],
]);
if (
$description_footer = $this->getPodcast()
->episode_description_footer
) {
return $converter->convertToHtml(
$this->attributes['description'] . '---'
) . $converter->convertToHtml($description_footer);
}
return $converter->convertToHtml($this->attributes['description']);
}
}
<?php
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
/**
* Returns the inline svg icon
*
* @param string $name name of the icon file without the .svg extension
* @param string $class to be added to the svg string
* @return string svg contents
*/
function icon($name, $class = null)
{
$svg_contents = file_get_contents('assets/icons/' . $name . '.svg');
if ($class) {
$svg_contents = str_replace(
'<svg',
'<svg class="' . $class . '"',
$svg_contents
);
}
return $svg_contents;
}
/**
* Returns the inline svg image
*
* @param string $name name of the image file without the .svg extension
* @param string $class to be added to the svg string
* @return string svg contents
*/
function svg($name, $class = null)
{
$svg_contents = file_get_contents('assets/images/' . $name . '.svg');
if ($class) {
$svg_contents = str_replace(
'<svg',
'<svg class="' . $class . '"',
$svg_contents
);
}
return $svg_contents;
}
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
<?
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
return [
'dashboard' => 'Dashboard',
'podcasts' => 'Podcasts',
'users' => 'Users',
'admin_home' => 'Home',
'podcast_list' => 'All podcasts',
'podcast_create' => 'New podcast',
'user_list' => 'All users',
'user_create' => 'New user',
'go_to_website' => 'Go to website'
];
\ No newline at end of file
......@@ -11,8 +11,9 @@ return [
'create' => 'Create a Podcast',
'new_episode' => 'New Episode',
'feed' => 'RSS feed',
'edit' => 'Edit',
'delete' => 'Delete',
'view' => 'View podcast',
'edit' => 'Edit podcast',
'delete' => 'Delete podcast',
'see_episodes' => 'See episodes',
'see_contributors' => 'See contributors',
'goto_page' => 'Go to page',
......
......@@ -125,6 +125,10 @@ class PodcastModel extends Model
$podcast_permissions = [
'podcasts:' . $podcast->id => [
[
'name' => 'View',
'description' => "View the $podcast->name podcast",
],
[
'name' => 'edit',
'description' => "Edit the $podcast->name podcast",
......
import Dropdown from "./modules/Dropdown";
import HTMLEditor from "./modules/HTMLEditor";
import MarkdownEditor from "./modules/MarkdownEditor";
import Slugify from "./modules/Slugify";
import Tooltip from "./modules/Tooltip";
Dropdown();
Tooltip();
MarkdownEditor();
HTMLEditor();
Slugify();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M12 14l-4-4h8z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M13 21V11h8v10h-8zM3 13V3h8v10H3zm6-2V5H5v6h4zM3 21v-6h8v6H3zm2-2h4v-2H5v2zm10 0h4v-6h-4v6zM13 3h8v6h-8V3zm2 2v2h4V5h-4z"/>
</g>
</svg>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment