Compare commits

..

3 Commits

11 changed files with 178 additions and 11 deletions

View File

@ -28,6 +28,7 @@ GET /ticket/@id/attachments=AttachmentController->index
POST /ticket/@id/attachments/upload=AttachmentController->upload
GET /attachment/@id/download=AttachmentController->download
GET /attachment/@id/delete=AttachmentController->delete
GET /attachment/@id/view=AttachmentController->view
; knowledgebase
GET /kb=KBController->index

View File

@ -145,4 +145,41 @@ class AttachmentController {
// remove DB row
$db->exec('DELETE FROM attachments WHERE id =?', [$attachment_id]);
}
// view attachment
public function view($f3){
$this->check_access($f3);
$attachment_id = (int) $f3->get('PARAMS.id');
$db = $f3->get('DB');
$rows = $db->exec('SELECt * FROM attachments WHERE id = ?', [$attachment_id]);
if(!$rows){
$f3->error(404, "File not found");
return;
}
$attachment = $rows[0];
$file_path = $attachment['path'];
$file_name = $attachment['file_name'];
if(!file_exists($file_path)){
$f3->error(404, "File not found");
return;
}
// detect mime type
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $file_path);
finfo_close($finfo);
header('Content-Type: ' . $mime_type);
header('Content-Disposition: inline; filename="' . basename($file_name) . '"');
header('Content-Length: ' . filesize($file_path));
flush();
readfile($file_path);
exit;
}
}

View File

@ -58,6 +58,7 @@ class TicketController extends BaseController implements CRUD {
$data = [
'title' => $this->f3->get('POST.title'),
'created_at' => $this->f3->get('POST.created_at'),
'description' => $this->f3->get('POST.description'),
'priority' => $this->f3->get('POST.priority'),
'status' => $this->f3->get('POST.status'),
@ -115,6 +116,7 @@ class TicketController extends BaseController implements CRUD {
$data = [
'title' => $this->f3->get('POST.title'),
'created_at' => $this->f3->get('POST.created_at'),
'description' => $this->f3->get('POST.description'),
'priority' => $this->f3->get('POST.priority'),
'status' => $this->f3->get('POST.status'),

View File

@ -8,7 +8,7 @@ class ParsedownHelper extends \Prefab {
$return = \Parsedown::instance()->text($args[0]);
return '
return '<!-- tableextension -->
<div class="content">
<parsedown_rendered>'.$return.'</parsedown_rendered>
</div>
@ -28,7 +28,7 @@ class ParsedownHelper extends \Prefab {
}
function build($content){
return Parsedown::instance()->text($content);
return \ParsedownTableExtension::instance()->text($content);
}
}

View File

@ -0,0 +1,28 @@
<?php
class ParsedownTableExtension extends Parsedown
{
protected function blockTable($Line, ?array $Block = null)
{
$Block = parent::blockTable($Line, $Block);
if(!isset($Block)){
return null;
}
// add classes to the table element
$Block['element']['attributes'] = [
'class' => 'table is-bordered',
];
// wrap the table in a bulma div
$Block['element'] = [
'name' => 'div',
'attributes' => [
'class' => 'table-container'
],
'handler' => 'element',
'text' => $Block['element'],
];
return $Block;
}
}

View File

@ -33,7 +33,7 @@ class Ticket extends \DB\SQL\Mapper {
$this->priority = $data['priority'] ?? 'Low';
$this->status = $data['status'] ?? 'New';
$this->created_by = $data['created_by'] ?? null;
$this->created_at = date('Y-m-d H:i:s');
$this->created_at = $data['created_at'] ?? date('Y-m-d H:i:s');
$this->updated_at = date('Y-m-d H:i:s');
$this->save();
@ -43,10 +43,11 @@ class Ticket extends \DB\SQL\Mapper {
public function updateTicket(array $data): void
{
if(isset($data['title'])){ $this->title = $data['title']; }
if(isset($data['description'])) { $this->descrtipion = $data['description']; }
if(isset($data['description'])) { $this->description = $data['description']; }
if(isset($data['priority'])) { $this->priority = $data['priority']; }
if(isset($data['status'])) { $this->status = $data['status']; }
if(isset($data['updated_by'])) { $this->status = $data['updated_by']; }
if(isset($data['updated_by'])) { $this->updated_by = $data['updated_by']; }
$this->created_at = ($data['created_at'] == '' ? date('Y-m-d H:i:s') : $data['created_at']) ?? date('Y-m-d H:i:s');
$this->updated_at = date('Y-m-d H:i:s');
$this->save();
}

View File

@ -3,18 +3,18 @@
require '../lib/autoload.php';
$f3 = \Base::instance();
$f3->config('../app/config/.env.cfg');
$f3->set('DEBUG', 3); // development debug
$f3->set('CACHE', FALSE);
/**
* Not required yet
*/
$htmlpurifier = \HTMLPurifier::instance();
// $htmlpurifier->purify($input);
$md = \Parsedown::instance();
$md = \ParsedownTableExtension::instance();
$md->setSafeMode(true);
$f3->config('../app/config/.env.cfg');
$f3->set('DEBUG', 3); // development debug
$f3->set('CACHE', FALSE);
$f3->set('EXT', [new ParsedownHelper, new BulmaFormHelper]);
$f3->set('DB', new \DB\SQL(

89
ui/parts/clipboard.html Normal file
View File

@ -0,0 +1,89 @@
<div class="block">
<style>
#upload-area {
height: 250px;
border: 2px dashed #aaa;
display: flex;
align-items: center;
margin-top: 10px;
color: #555;
font-family: sans-serif;
text-align: center;
}
#upload-area.hover {
background-color: #f0f0f0;
border-color: #444;
}
#preview {
max-width:100%;
margin-top: 10px;
}
</style>
<div id="upload-area" contenteditable="true">
Paste or drag an image here
</div>
<img id="preview" alt="image preview" hidden>
<p id="status"></p>
<script>
const area = document.getElementById('upload-area');
const preview = document.getElementById('preview');
const status = document.getElementById('status');
async function uploadImage(file){
const reader = new FileReader();
reader.onload = () => {
preview.src = reader.result;
preview.hidden = false;
};
reader.readAsDataURL(file);
const formData = new FormData();
formData.append('attachment', file);
try {
const res = await fetch('/ticket/{{@ticket->id}}/attachments/upload', {
method: 'POST',
body: formData
});
const text = await res.text();
status.textContent = text;
} catch (e) {
status.textContent = 'upload failed.'
}
}
// paste
area.addEventListener('paste', (e) => {
for(let item of e.clipboardData.items){
if(item.type.startsWith('image/')){
uploadImage(item.getAsFile());
}
}
});
// drag and drop
area.addEventListener('dragover', (e) => {
e.preventDefault();
area.classList.add('hover');
});
area.addEventListener('dragLeave', ()=> {
area.classList.remove('hover');
});
area.addEventListener('drop', (e) => {
e.preventDefault();
area.classList.remove('hover');
const files = e.dataTransfer.files;
for(let file of files){
if(file.type.startsWith('image/')){
uploadImage(file);
}
}
});
</script>
</div>

View File

@ -28,6 +28,12 @@
</repeat>
</tbody>
</table>
<hr>
<div>
<repeat group="{{ @attachments }}" value="{{ @attach }}">
<img src="/attachment/{{@attach.id}}/view">
</repeat>
</div>
</check>
</check>
<div class="block">

View File

@ -4,9 +4,10 @@
<form action="/ticket/create" method="POST">
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value=""></bulma>
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value=""></bulma>
<!-- TODO implement priority and status -->
<bulma type="H_FIELD_SELECT" label="Priority:" name="priority" options="['Low', 'Medium', 'High']" selected="Medium"></bulma>
<bulma type="H_FIELD_SELECT" label="Status:" name="status" options="['New', 'In Progress', 'On Hold', 'Completed']" selected="New"></bulma>

View File

@ -3,12 +3,14 @@
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
<bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@ticket.title}}"></bulma>
<bulma type="H_FIELD_INPUT" label="Created At:" name="created_at" value="{{@ticket.created_at}}"></bulma>
<bulma type="H_FIELD_TEXTAREA" label="Description:" name="description" value="{{@ticket.description}}"></bulma>
<bulma type="H_FIELD_SELECT" label="Priority:" name="priority" options="['Low', 'Medium', 'High']" selected="{{@ticket.priority}}"></bulma>
<bulma type="H_FIELD_SELECT" label="Status:" name="status" options="['New', 'In Progress', 'On Hold', 'Completed']" selected="{{@ticket.status}}"></bulma>
<include href="/ui/parts/clipboard.html"></include>
<div class="block">
<h3 class="title is-5">Custom Fields</h3>