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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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(){
|
||||
|
@@ -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' },
|
||||
|
@@ -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 |
@@ -16,6 +16,7 @@ formats:
|
||||
- 'notice'
|
||||
- 'image'
|
||||
- 'video'
|
||||
- 'audio'
|
||||
- 'file'
|
||||
- 'toc'
|
||||
- 'hr'
|
||||
|
@@ -126,6 +126,7 @@ fieldsetwriting:
|
||||
'notice': 'notice'
|
||||
'image': 'image'
|
||||
'video': 'video'
|
||||
'audio': 'audio'
|
||||
'file': 'file'
|
||||
'toc': 'table of contents'
|
||||
'hr': 'horizontal line'
|
||||
|
Reference in New Issue
Block a user