1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-28 18:00:28 +02:00

V2.11.0 native audio uploads

This commit is contained in:
trendschau
2024-10-27 21:41:50 +01:00
parent 8520c564e3
commit a8e85cf28d
6 changed files with 370 additions and 0 deletions

View File

@@ -110,5 +110,68 @@ class MediaExtension implements EventSubscriberInterface
$shortcode->setData($html);
}
if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'audio' && isset($shortcodeArray['params']['path']))
{
$relUrl = $shortcodeArray['params']['path'];
$relUrl = '/' . trim($relUrl, '/');
# Convert the relative URL to an absolute file path
$filePath = $this->rootpath . $relUrl;
# check file exists
if(!file_exists($filePath))
{
$html = '<p style="color:red">File not found</p>';
}
else
{
# Get file extension using pathinfo()
$fileInfo = pathinfo($filePath);
$extension = strtolower($fileInfo['extension']); // Get file extension and convert to lowercase
$absUrl = $this->baseurl . $relUrl;
# Determine the correct file type for the video tag
$type = '';
switch ($extension) {
case 'mp3':
$type = 'mp3';
break;
case 'ogg':
$type = 'ogg';
break;
default:
$html = '<p style="color:red">Unsupported file type</p>';
return; // Exit if file type is not supported
}
$preload = 'none';
if(isset($shortcodeArray['params']['preload']) && ($shortcodeArray['params']['preload'] == 'auto' or $shortcodeArray['params']['preload'] == 'metadata'))
{
$preload = $shortcodeArray['params']['preload'];
}
$width = $shortcodeArray['params']['width'] ?? '500';
if (!preg_match('/^(\d+)(px|%)?$/', $width))
{
$width = '500';
}
$html = '<audio
controls
style="width:' . $width . '; max-width:100%"
preload="' . $preload . '"
class="center"
>
<source src="' . $absUrl . '" type="audio/' . $type . '" />
Download the
<a href="' . $absUrl . '">' . $type . '</a>
audio.
</audio>';
}
$shortcode->setData($html);
}
}
}

View File

@@ -2640,6 +2640,299 @@ bloxeditor.component('video-component', {
}
})
bloxeditor.component('audio-component', {
props: ['markdown', 'disabled', 'index'],
components: {
medialib: medialib
},
template: `<div class="dropbox">
<input type="hidden" ref="markdown" :value="markdown" :disabled="disabled" @input="updatemarkdown" />
<div class="flex">
<div class="imageupload relative w-1/2 border-r border-dotted border-stone-700">
<input type="file" name="file" accept="audio/mpeg, audio/ogg" class="opacity-0 w-full h-24 absolute cursor-pointer z-10" @change="onFileChange( $event )" />
<p class="text-center p-6">
<svg class="icon icon-upload">
<use xlink:href="#icon-upload"></use>
</svg>
{{ $filters.translate('upload audio') }}
</p>
</div>
<button class="imageselect w-1/2 text-center p-6" @click.prevent="openmedialib('files')">
<svg class="icon icon-paperclip baseline">
<use xlink:href="#icon-paperclip"></use>
</svg>
{{ $filters.translate('select from medialib') }}
</button>
</div>
<Transition name="initial" appear>
<div v-if="showmedialib" class="fixed top-0 left-0 right-0 bottom-0 bg-stone-100 z-50">
<button class="w-full bg-stone-200 hover:bg-rose-500 hover:text-white p-2 transition duration-100" @click.prevent="showmedialib = false">{{ $filters.translate('close library') }}</button>
<medialib parentcomponent="files" @addFromMedialibEvent="addFromMedialibFunction"></medialib>
</div>
</Transition>
<div class="absolute top-3 -left-5 text-stone-400">
<svg class="icon icon-paperclip">
<use xlink:href="#icon-paperclip"></use>
</svg>
</div>
<div v-if="load" class="loadwrapper"><span class="load"></span></div>
<div class="imgmeta p-8" v-if="filemeta">
<input
title = "fileid"
type = "hidden"
placeholder = "id"
v-model = "fileid"
@input = "createmarkdown"
max = "140"
/>
<div class="flex mb-2">
<label class="w-1/5 py-2" for="path">{{ $filters.translate('Path') }}: </label>
<input class="w-4/5 p-2 bg-stone-200 text-stone-900" name="path" type="text" readonly="true" v-model="fileurl" />
</div>
<div class="flex mb-2">
<label class="w-1/5 py-2" for="width">{{ $filters.translate('Width') }}: </label>
<input class="w-4/5 p-2 bg-stone-200 text-stone-900" name="width" type="text" placeholder="500" v-model="width" @input="createmarkdown" />
</div>
<div class="flex mb-2">
<label class="w-1/5 py-2" for="videopreload">{{ $filters.translate('Preload') }}: </label>
<select class="w-4/5 p-2 bg-stone-200 text-stone-900" name="videopreload" v-model="preload" @change="createmarkdown">
<option value="none">none</option>
<option value="metadata">metadata</option>
<option value="auto">auto</option>
</select>
</div>
</div>
</div>`,
data: function(){
return {
maxsize: 100, // megabyte
showmedialib: false,
load: false,
filemeta: false,
fileextension: '',
allowedExtensions: ['mp3', 'ogg'],
fileurl: '',
width: '500px',
fileid: '',
savefile: false,
preload: 'none',
}
},
mounted: function() {
eventBus.$on('beforeSave', this.beforeSave);
this.$refs.markdown.focus();
if (this.markdown)
{
this.filemeta = true;
var fileurl = this.markdown.match(/path="(.*?)"/);
if (fileurl && fileurl[1])
{
this.fileurl = fileurl[1];
}
var width = this.markdown.match(/width="(.*?)"/);
if (width && width[1])
{
this.width = width[1];
}
var preload = this.markdown.match(/preload="(.*?)"/);
if (preload && preload[1])
{
this.preload = preload[1];
}
}
},
methods: {
addFromMedialibFunction(file)
{
this.showmedialib = false;
this.savefile = false;
this.filemeta = true;
if (this.allowedExtensions.includes(file.info.extension.toLowerCase()))
{
this.filetitle = file.name;
this.fileextension = file.info.extension.toLowerCase();
this.fileurl = file.url;
}
else
{
let message = "Unsupported file type. Please select a valid audio file (mp3, ogg).";
eventBus.$emit('publishermessage', message);
return;
}
this.createmarkdown();
},
openmedialib()
{
this.showmedialib = true;
},
isChecked(classname)
{
if(this.fileclass == classname)
{
return ' checked';
}
},
updatemarkdown(event, url)
{
this.$emit('updateMarkdownEvent', event.target.value);
},
createmarkdown()
{
var errors = false;
var filemarkdown = false;
if (this.fileurl !== '')
{
if (this.fileurl.length < 101)
{
var width = this.width ? ' width="' + this.width + '"' : '';
var preload = this.preload ? ' preload="' + this.preload + '"' : ' preload="none"';
filemarkdown = '[:audio path="' + this.fileurl + '"' + width + preload + ' :]';
}
else
{
errors = this.$filters.translate('Maximum size of file link is 100 characters');
}
}
if (errors)
{
eventBus.$emit('publishermessage', this.$filters.translate(errors));
}
else if (filemarkdown)
{
this.$emit('updateMarkdownEvent', filemarkdown);
this.compmarkdown = filemarkdown;
}
},
onFileChange( e )
{
if(e.target.files.length > 0)
{
let uploadedFile = e.target.files[0];
let allowedAudioTypes = ['audio/mpeg', 'audio/ogg'];
if (!allowedAudioTypes.includes(uploadedFile.type)) {
let message = "Unsupported file type. Please select an audio file (mp3, ogg).";
eventBus.$emit('publishermessage', message);
return;
}
let size = uploadedFile.size / 1024 / 1024;
if (size > this.maxsize)
{
let message = "The maximal size of a file is " + this.maxsize + " MB";
eventBus.$emit('publishermessage', message);
return;
}
else
{
self = this;
self.load = true;
let reader = new FileReader();
reader.readAsDataURL(uploadedFile);
reader.onload = function(e) {
tmaxios.post('/api/v1/file',{
'url': data.urlinfo.route,
'file': e.target.result,
'name': uploadedFile.name,
})
.then(function (response) {
self.load = false;
self.filemeta = true;
self.savefile = true;
self.filetitle = response.data.fileinfo.title;
self.fileextension = response.data.fileinfo.extension;
self.fileurl = response.data.filepath;
self.selectedrole = '';
self.createmarkdown();
})
.catch(function (error)
{
self.load = false;
if(error.response)
{
let message = self.$filters.translate(error.response.data.message);
eventBus.$emit('publishermessage', message);
}
});
}
}
}
},
beforeSave()
{
/* publish file before you save markdown */
if(!this.fileurl)
{
let message = this.$filters.translate('file is missing.');
eventBus.$emit('publishermessage', message);
return;
}
const fileExtension = this.fileurl.split('.').pop().toLowerCase();
if (!this.allowedExtensions.includes(fileExtension))
{
let message = this.$filters.translate('Unsupported file format. Only MP3, and OGG files are allowed.');
eventBus.$emit('publishermessage', message);
return;
}
if(!this.savefile)
{
this.createmarkdown();
this.$emit('saveBlockEvent');
}
else
{
var self = this;
tmaxios.put('/api/v1/file',{
'url': data.urlinfo.route,
'file': this.fileurl,
})
.then(function (response)
{
self.fileurl = response.data.path;
self.createmarkdown();
self.$emit('saveBlockEvent');
})
.catch(function (error)
{
if(error.response)
{
let message = self.$filters.translate(error.response.data.message);
eventBus.$emit('publishermessage', message);
}
});
}
},
}
})
bloxeditor.component('shortcode-component', {
props: ['markdown', 'disabled', 'index'],
data: function(){

View File

@@ -74,6 +74,13 @@ const determiner = {
}
return false;
},
audio: function(block,lines,firstChar,secondChar,thirdChar){
if (lines[0].startsWith('[:audio'))
{
return "audio-component";
}
return false;
},
code: function(block,lines,firstChar,secondChar,thirdChar){
if( firstChar == '`' && secondChar == '`' && thirdChar == '`')
{
@@ -114,6 +121,7 @@ const bloxFormats = {
notice: { label: '<svg class="icon icon-exclamation-circle"><use xlink:href="#icon-exclamation-circle"></use></svg>', title: 'Notice', component: 'notice-component' },
image: { label: '<svg class="icon icon-image"><use xlink:href="#icon-image"></use></svg>', title: 'Image', component: 'image-component' },
video: { label: '<svg class="icon icon-film"><use xlink:href="#icon-film"></use></svg>', title: 'Video', component: 'video-component' },
audio: { label: '<svg class="icon icon-music"><use xlink:href="#icon-music"></use></svg>', title: 'Audio', component: 'audio-component' },
file: { label: '<svg class="icon icon-paperclip"><use xlink:href="#icon-paperclip"></use></svg>', title: 'File', component: 'file-component' },
toc: { label: '<svg class="icon icon-list-alt"><use xlink:href="#icon-list-alt"></use></svg>', title: 'Table of Contents', component: 'toc-component' },
hr: { label: '<svg class="icon icon-pagebreak"><use xlink:href="#icon-pagebreak"></use></svg>', title: 'Horizontal Line', component: 'hr-component' },

View File

@@ -46,6 +46,10 @@
<title>{{ translate('VIDEO') }}</title>
<path d="M0 4v24h32v-24h-32zM6 26h-4v-4h4v4zM6 18h-4v-4h4v4zM6 10h-4v-4h4v4zM24 26h-16v-20h16v20zM30 26h-4v-4h4v4zM30 18h-4v-4h4v4zM30 10h-4v-4h4v4zM12 10v12l8-6z"></path>
</symbol>
<symbol id="icon-music" viewBox="0 0 32 32">
<title>{{ translate('AUDIO') }}</title>
<path d="M30 0h2v23c0 2.761-3.134 5-7 5s-7-2.239-7-5c0-2.761 3.134-5 7-5 1.959 0 3.729 0.575 5 1.501v-11.501l-16 3.556v15.444c0 2.761-3.134 5-7 5s-7-2.239-7-5c0-2.761 3.134-5 7-5 1.959 0 3.729 0.575 5 1.501v-19.501l18-4z"></path>
</symbol>
<symbol id="icon-quotes-left" viewBox="0 0 32 32">
<title>{{ translate('QUOTES') }}</title>
<path d="M7.031 14c3.866 0 7 3.134 7 7s-3.134 7-7 7-7-3.134-7-7l-0.031-1c0-7.732 6.268-14 14-14v4c-2.671 0-5.182 1.040-7.071 2.929-0.364 0.364-0.695 0.751-0.995 1.157 0.357-0.056 0.724-0.086 1.097-0.086zM25.031 14c3.866 0 7 3.134 7 7s-3.134 7-7 7-7-3.134-7-7l-0.031-1c0-7.732 6.268-14 14-14v4c-2.671 0-5.182 1.040-7.071 2.929-0.364 0.364-0.695 0.751-0.995 1.157 0.358-0.056 0.724-0.086 1.097-0.086z"></path>

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -16,6 +16,7 @@ formats:
- 'notice'
- 'image'
- 'video'
- 'audio'
- 'file'
- 'toc'
- 'hr'

View File

@@ -126,6 +126,7 @@ fieldsetwriting:
'notice': 'notice'
'image': 'image'
'video': 'video'
'audio': 'audio'
'file': 'file'
'toc': 'table of contents'
'hr': 'horizontal line'