mirror of
https://github.com/typemill/typemill.git
synced 2025-07-31 19:30:40 +02:00
V2.10.0 introduse native videos to visual editor
This commit is contained in:
46
composer.lock
generated
46
composer.lock
generated
@@ -116,12 +116,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/erusev/parsedown-extra.git",
|
||||
"reference": "fd33d68349630d18d56367712a64445a6e0bc83e"
|
||||
"reference": "cd57cf183be7b56e90498091c0a7955ad4df223c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/fd33d68349630d18d56367712a64445a6e0bc83e",
|
||||
"reference": "fd33d68349630d18d56367712a64445a6e0bc83e",
|
||||
"url": "https://api.github.com/repos/erusev/parsedown-extra/zipball/cd57cf183be7b56e90498091c0a7955ad4df223c",
|
||||
"reference": "cd57cf183be7b56e90498091c0a7955ad4df223c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -163,7 +163,7 @@
|
||||
"issues": "https://github.com/erusev/parsedown-extra/issues",
|
||||
"source": "https://github.com/erusev/parsedown-extra/tree/master"
|
||||
},
|
||||
"time": "2021-10-25T08:46:29+00:00"
|
||||
"time": "2024-09-29T09:07:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fig/http-message-util",
|
||||
@@ -406,16 +406,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.3.4",
|
||||
"version": "v1.3.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/serializable-closure.git",
|
||||
"reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81"
|
||||
"reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
|
||||
"reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c",
|
||||
"reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -463,7 +463,7 @@
|
||||
"issues": "https://github.com/laravel/serializable-closure/issues",
|
||||
"source": "https://github.com/laravel/serializable-closure"
|
||||
},
|
||||
"time": "2024-08-02T07:48:17+00:00"
|
||||
"time": "2024-09-23T13:33:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/fast-route",
|
||||
@@ -1452,16 +1452,16 @@
|
||||
},
|
||||
{
|
||||
"name": "slim/twig-view",
|
||||
"version": "3.4.0",
|
||||
"version": "3.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/slimphp/Twig-View.git",
|
||||
"reference": "1b351536b9a07ed90a3563ee9d71a987c5d74610"
|
||||
"reference": "b4268d87d0e327feba5f88d32031e9123655b909"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/slimphp/Twig-View/zipball/1b351536b9a07ed90a3563ee9d71a987c5d74610",
|
||||
"reference": "1b351536b9a07ed90a3563ee9d71a987c5d74610",
|
||||
"url": "https://api.github.com/repos/slimphp/Twig-View/zipball/b4268d87d0e327feba5f88d32031e9123655b909",
|
||||
"reference": "b4268d87d0e327feba5f88d32031e9123655b909",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1469,12 +1469,12 @@
|
||||
"psr/http-message": "^1.1 || ^2.0",
|
||||
"slim/slim": "^4.12",
|
||||
"symfony/polyfill-php81": "^1.29",
|
||||
"twig/twig": "^3.8"
|
||||
"twig/twig": "^3.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/phpstan": "^1.10.59",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"phpunit/phpunit": "^9.6 || ^10",
|
||||
"psr/http-factory": "^1.0",
|
||||
"squizlabs/php_codesniffer": "^3.9"
|
||||
},
|
||||
@@ -1511,9 +1511,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/slimphp/Twig-View/issues",
|
||||
"source": "https://github.com/slimphp/Twig-View/tree/3.4.0"
|
||||
"source": "https://github.com/slimphp/Twig-View/tree/3.4.1"
|
||||
},
|
||||
"time": "2024-04-28T20:36:39+00:00"
|
||||
"time": "2024-09-26T05:42:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -2126,16 +2126,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/yaml",
|
||||
"version": "v5.4.43",
|
||||
"version": "v5.4.44",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/yaml.git",
|
||||
"reference": "62f96e1cfd4cf518882a36bfedcf1fe4093c1299"
|
||||
"reference": "7025b964f123bbf1896d7563db6ec7f1f63e918a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/62f96e1cfd4cf518882a36bfedcf1fe4093c1299",
|
||||
"reference": "62f96e1cfd4cf518882a36bfedcf1fe4093c1299",
|
||||
"url": "https://api.github.com/repos/symfony/yaml/zipball/7025b964f123bbf1896d7563db6ec7f1f63e918a",
|
||||
"reference": "7025b964f123bbf1896d7563db6ec7f1f63e918a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2181,7 +2181,7 @@
|
||||
"description": "Loads and dumps YAML files",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/yaml/tree/v5.4.43"
|
||||
"source": "https://github.com/symfony/yaml/tree/v5.4.44"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2197,7 +2197,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-08-11T17:40:32+00:00"
|
||||
"time": "2024-09-16T14:36:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
|
114
system/typemill/Extensions/MediaExtension.php
Normal file
114
system/typemill/Extensions/MediaExtension.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
class MediaExtension implements EventSubscriberInterface
|
||||
{
|
||||
private $rootpath;
|
||||
|
||||
private $baseurl;
|
||||
|
||||
function __construct($rootpath, $baseurl)
|
||||
{
|
||||
$this->rootpath = $rootpath;
|
||||
$this->baseurl = $baseurl;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
'onShortcodeFound' => 'onShortcodeFound',
|
||||
];
|
||||
}
|
||||
|
||||
public function onShortcodeFound($shortcode)
|
||||
{
|
||||
$shortcodeArray = $shortcode->getData();
|
||||
|
||||
if(is_array($shortcodeArray) && $shortcodeArray['name'] == 'video' && 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 'mp4':
|
||||
$type = 'mp4';
|
||||
break;
|
||||
case 'webm':
|
||||
$type = 'webm';
|
||||
break;
|
||||
case 'ogg':
|
||||
$type = 'ogg';
|
||||
break;
|
||||
default:
|
||||
$html = '<p style="color:red">Unsupported file type</p>';
|
||||
return; // Exit if file type is not supported
|
||||
}
|
||||
|
||||
$width = $shortcodeArray['params']['width'] ?? '500';
|
||||
if (!preg_match('/^(\d+)(px|%)?$/', $width))
|
||||
{
|
||||
$width = '500';
|
||||
}
|
||||
|
||||
$preload = 'none';
|
||||
if(isset($shortcodeArray['params']['preload']) && ($shortcodeArray['params']['preload'] == 'auto' or $shortcodeArray['params']['preload'] == 'metadata'))
|
||||
{
|
||||
$preload = $shortcodeArray['params']['preload'];
|
||||
}
|
||||
|
||||
$poster = '';
|
||||
|
||||
if(isset($shortcodeArray['params']['poster']))
|
||||
{
|
||||
$relImgUrl = $shortcodeArray['params']['poster'];
|
||||
$relImgUrl = '/' . trim($relImgUrl, '/');
|
||||
|
||||
# Convert the relative URL to an absolute file path
|
||||
$imgPath = $this->rootpath . $relImgUrl;
|
||||
|
||||
# check file exists
|
||||
if(file_exists($imgPath))
|
||||
{
|
||||
$absImgUrl = $this->baseurl . $relImgUrl;
|
||||
$poster = ' poster="' . $absImgUrl . '"';
|
||||
}
|
||||
}
|
||||
|
||||
$html = '<video
|
||||
controls
|
||||
width = "' . $width . '"
|
||||
preload = "' . $preload . '"
|
||||
' . $poster . '
|
||||
class = "center"
|
||||
>
|
||||
<source src="' . $absUrl . '" type="video/' . $type . '" />
|
||||
Download the
|
||||
<a href="' . $absUrl . '">' . $type . '</a>
|
||||
video.
|
||||
</video>';
|
||||
}
|
||||
|
||||
$shortcode->setData($html);
|
||||
}
|
||||
}
|
||||
}
|
@@ -409,7 +409,7 @@
|
||||
.blox-preview .notice3:before{ content: "!!!"; }
|
||||
.blox-preview .notice4:before{ content: "!!!!"; }
|
||||
|
||||
.blox-preview img, img.uploadPreview{
|
||||
.blox-preview video, .blox-preview audio, .blox-preview img, img.uploadPreview{
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 100%;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2306,128 +2306,338 @@ bloxeditor.component('file-component', {
|
||||
|
||||
bloxeditor.component('video-component', {
|
||||
props: ['markdown', 'disabled', 'index'],
|
||||
template: `<div class="video dropbox p-8">
|
||||
<div class="absolute top-3 -left-5 text-stone-400">
|
||||
<svg class="icon icon-play">
|
||||
<use xlink:href="#icon-play"></use>
|
||||
</svg>
|
||||
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="video/mp4,video/webm,video/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 video') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex mt-2 mb-2">
|
||||
<label class="w-1/5 py-2" for="video">{{ $filters.translate('Link to youtube') }}: </label>
|
||||
<input class="w-4/5 p-2 bg-stone-200 text-stone-900" type="url" ref="markdown" placeholder="https://www.youtube.com/watch?v=" :value="markdown" :disabled="disabled" @input="updatemarkdown($event.target.value)">
|
||||
<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 == 'files'" 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>
|
||||
</div>`,
|
||||
</Transition>
|
||||
<Transition name="initial" appear>
|
||||
<div v-if="showmedialib == 'images'" 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="images" @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 class="flex mb-2">
|
||||
<label class="w-1/5 py-2" for="imagepath">{{ $filters.translate('Image') }}: </label>
|
||||
<div class="flex w-4/5 justify-between">
|
||||
<button @click.prevent="deleteImage()" class="w-8 bg-rose-500 dark:bg-stone-600 hover:bg-rose-600 hover:dark:bg-rose-500 text-white">x</button>
|
||||
<input class="w-full p-2 bg-stone-200 text-stone-900" name="path" type="text" readonly="true" v-model="imageurl" />
|
||||
<button @click.prevent="openmedialib('images')" class="w-8 bg-stone-600 dark:bg-stone-600 hover:bg-stone-800 hover:dark:bg-stone-500 text-white"><svg class="icon icon-image"><use xlink:href="#icon-image"></use></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`,
|
||||
data: function(){
|
||||
return {
|
||||
edited: false,
|
||||
url: false,
|
||||
videoid: false,
|
||||
param: false,
|
||||
path: false,
|
||||
provider: false,
|
||||
providerurl: false,
|
||||
compmarkdown: '',
|
||||
maxsize: 100, // megabyte
|
||||
showmedialib: false,
|
||||
load: false,
|
||||
filemeta: false,
|
||||
fileextension: '',
|
||||
allowedImageExtensions: ['webp', 'png', 'svg', 'jpg', 'jpeg'],
|
||||
allowedExtensions: ['mp4', 'webm', 'ogg'],
|
||||
fileurl: '',
|
||||
width: '500',
|
||||
fileid: '',
|
||||
imageurl: '',
|
||||
savefile: false,
|
||||
mediatypes: 'files',
|
||||
preload: 'none',
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
mounted: function() {
|
||||
eventBus.$on('beforeSave', this.beforeSave);
|
||||
|
||||
eventBus.$on('beforeSave', this.beforeSave );
|
||||
this.$refs.markdown.focus();
|
||||
|
||||
this.$refs.markdown.focus();
|
||||
if (this.markdown)
|
||||
{
|
||||
this.filemeta = true;
|
||||
|
||||
if(this.markdown)
|
||||
{
|
||||
this.parseImageMarkdown(this.markdown);
|
||||
}
|
||||
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];
|
||||
}
|
||||
|
||||
var poster = this.markdown.match(/poster="(.*?)"/);
|
||||
if (poster && poster[1])
|
||||
{
|
||||
this.imageurl = poster[1];
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateMarkdown()
|
||||
addFromMedialibFunction(file)
|
||||
{
|
||||
this.compmarkdown = '{#' + this.videoid + ' .' + this.provider + '}';
|
||||
this.showmedialib = false;
|
||||
this.savefile = false;
|
||||
this.filemeta = true;
|
||||
|
||||
if (typeof file === 'string')
|
||||
{
|
||||
let fileExtension = file.split('.').pop().toLowerCase();
|
||||
|
||||
if (this.allowedImageExtensions.includes(fileExtension))
|
||||
{
|
||||
this.imageurl = file;
|
||||
}
|
||||
else
|
||||
{
|
||||
let message = "Unsupported file type. Please select an image with format webp, png, jpg, jpeg. svg.";
|
||||
eventBus.$emit('publishermessage', message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else 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 video file (webm, mp4, ogg).";
|
||||
eventBus.$emit('publishermessage', message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.createmarkdown();
|
||||
},
|
||||
parseImageMarkdown(imageMarkdown)
|
||||
openmedialib(type)
|
||||
{
|
||||
let regexpurl = /\((.*)(".*")\)/;
|
||||
let match = imageMarkdown.match(regexpurl);
|
||||
let imageUrl = match[1];
|
||||
|
||||
let regexprov = /live\/(.*?)-/;
|
||||
let matchprov = imageUrl.match(regexprov);
|
||||
this.provider = matchprov[1];
|
||||
|
||||
if(this.provider == 'youtube')
|
||||
{
|
||||
this.providerurl = "https://www.youtube.com/watch";
|
||||
this.param = "v=";
|
||||
}
|
||||
|
||||
let videoid = imageMarkdown.match(/#.*? /);
|
||||
if(videoid)
|
||||
{
|
||||
this.videoid = videoid[0].trim().substring(1);
|
||||
}
|
||||
|
||||
this.updatemarkdown(this.providerurl + "?" + this.param + this.videoid);
|
||||
this.showmedialib = type;
|
||||
},
|
||||
parseUrl(url)
|
||||
deleteImage()
|
||||
{
|
||||
let urlparts = url.split('?');
|
||||
let urlParams = new URLSearchParams(urlparts[1]);
|
||||
|
||||
this.providerurl = urlparts[0];
|
||||
|
||||
if(urlParams.has("v"))
|
||||
this.imageurl = '';
|
||||
},
|
||||
isChecked(classname)
|
||||
{
|
||||
if(this.fileclass == classname)
|
||||
{
|
||||
this.param = "v=";
|
||||
this.videoid = urlParams.get("v");
|
||||
this.provider = "youtube";
|
||||
}
|
||||
if(this.provider != "youtube")
|
||||
{
|
||||
this.updatemarkdown("");
|
||||
let message = this.$filters.translate("We only support youtube right now.");
|
||||
eventBus.$emit('publishermessage', message);
|
||||
return ' checked';
|
||||
}
|
||||
},
|
||||
updatemarkdown(url)
|
||||
updatemarkdown(event, url)
|
||||
{
|
||||
this.edited = true;
|
||||
this.url = url;
|
||||
this.parseUrl(url);
|
||||
this.generateMarkdown();
|
||||
this.$emit('updateMarkdownEvent', 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"';
|
||||
var poster = this.imageurl ? ' poster="' + this.imageurl + '"' : '';
|
||||
|
||||
filemarkdown = '[:video path="' + this.fileurl + '"' + width + preload + poster + ' :]';
|
||||
}
|
||||
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 allowedVideoTypes = ['video/mp4', 'video/webm', 'video/ogg'];
|
||||
if (!allowedVideoTypes.includes(uploadedFile.type)) {
|
||||
let message = "Unsupported file type. Please select a video file (mp4, webm, 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()
|
||||
{
|
||||
if(!this.edited)
|
||||
/* publish file before you save markdown */
|
||||
|
||||
if(!this.fileurl)
|
||||
{
|
||||
eventBus.$emit('closeComponents');
|
||||
let message = this.$filters.translate('file is missing.');
|
||||
eventBus.$emit('publishermessage', message);
|
||||
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/video',{
|
||||
'url': data.urlinfo.route,
|
||||
'videourl': this.url,
|
||||
'provider': this.provider,
|
||||
'providerurl': this.providerurl,
|
||||
'videoid': this.videoid,
|
||||
})
|
||||
.then(function (response)
|
||||
const fileExtension = this.fileurl.split('.').pop().toLowerCase();
|
||||
|
||||
if (!this.allowedExtensions.includes(fileExtension))
|
||||
{
|
||||
let message = this.$filters.translate('Unsupported file format. Only MP4, WebM, and OGG files are allowed.');
|
||||
eventBus.$emit('publishermessage', message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.savefile)
|
||||
{
|
||||
self.path = response.data.path;
|
||||
self.$emit('saveBlockEvent');
|
||||
})
|
||||
.catch(function (error)
|
||||
this.createmarkdown();
|
||||
this.$emit('saveBlockEvent');
|
||||
}
|
||||
else
|
||||
{
|
||||
if(error.response)
|
||||
var self = this;
|
||||
|
||||
tmaxios.put('/api/v1/file',{
|
||||
'url': data.urlinfo.route,
|
||||
'file': this.fileurl,
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
let message = self.$filters.translate(error.response.data.message);
|
||||
eventBus.$emit('publishermessage', message);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
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', {
|
||||
@@ -2593,4 +2803,131 @@ bloxeditor.component('shortcode-component', {
|
||||
this.$emit('updateMarkdownEvent', event.target.value);
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
/* deprecated, use embed plugin instead */
|
||||
bloxeditor.component('youtube-component', {
|
||||
props: ['markdown', 'disabled', 'index'],
|
||||
template: `<div class="video dropbox p-8">
|
||||
<div class="absolute top-3 -left-5 text-stone-400">
|
||||
<svg class="icon icon-play">
|
||||
<use xlink:href="#icon-play"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex mt-2 mb-2">
|
||||
<label class="w-1/5 py-2" for="video">{{ $filters.translate('Link to youtube') }}: </label>
|
||||
<input class="w-4/5 p-2 bg-stone-200 text-stone-900" type="url" ref="markdown" placeholder="https://www.youtube.com/watch?v=" :value="markdown" :disabled="disabled" @input="updatemarkdown($event.target.value)">
|
||||
</div>
|
||||
</div>`,
|
||||
data: function(){
|
||||
return {
|
||||
edited: false,
|
||||
url: false,
|
||||
videoid: false,
|
||||
param: false,
|
||||
path: false,
|
||||
provider: false,
|
||||
providerurl: false,
|
||||
compmarkdown: '',
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
eventBus.$on('beforeSave', this.beforeSave );
|
||||
|
||||
this.$refs.markdown.focus();
|
||||
|
||||
if(this.markdown)
|
||||
{
|
||||
this.parseImageMarkdown(this.markdown);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateMarkdown()
|
||||
{
|
||||
this.compmarkdown = '{#' + this.videoid + ' .' + this.provider + '}';
|
||||
},
|
||||
parseImageMarkdown(imageMarkdown)
|
||||
{
|
||||
let regexpurl = /\((.*)(".*")\)/;
|
||||
let match = imageMarkdown.match(regexpurl);
|
||||
let imageUrl = match[1];
|
||||
|
||||
let regexprov = /live\/(.*?)-/;
|
||||
let matchprov = imageUrl.match(regexprov);
|
||||
this.provider = matchprov[1];
|
||||
|
||||
if(this.provider == 'youtube')
|
||||
{
|
||||
this.providerurl = "https://www.youtube.com/watch";
|
||||
this.param = "v=";
|
||||
}
|
||||
|
||||
let videoid = imageMarkdown.match(/#.*? /);
|
||||
if(videoid)
|
||||
{
|
||||
this.videoid = videoid[0].trim().substring(1);
|
||||
}
|
||||
|
||||
this.updatemarkdown(this.providerurl + "?" + this.param + this.videoid);
|
||||
},
|
||||
parseUrl(url)
|
||||
{
|
||||
let urlparts = url.split('?');
|
||||
let urlParams = new URLSearchParams(urlparts[1]);
|
||||
|
||||
this.providerurl = urlparts[0];
|
||||
|
||||
if(urlParams.has("v"))
|
||||
{
|
||||
this.param = "v=";
|
||||
this.videoid = urlParams.get("v");
|
||||
this.provider = "youtube";
|
||||
}
|
||||
if(this.provider != "youtube")
|
||||
{
|
||||
this.updatemarkdown("");
|
||||
let message = this.$filters.translate("We only support youtube right now.");
|
||||
eventBus.$emit('publishermessage', message);
|
||||
}
|
||||
},
|
||||
updatemarkdown(url)
|
||||
{
|
||||
this.edited = true;
|
||||
this.url = url;
|
||||
this.parseUrl(url);
|
||||
this.generateMarkdown();
|
||||
this.$emit('updateMarkdownEvent', url);
|
||||
},
|
||||
beforeSave()
|
||||
{
|
||||
if(!this.edited)
|
||||
{
|
||||
eventBus.$emit('closeComponents');
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/video',{
|
||||
'url': data.urlinfo.route,
|
||||
'videourl': this.url,
|
||||
'provider': this.provider,
|
||||
'providerurl': this.providerurl,
|
||||
'videoid': this.videoid,
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.path = response.data.path;
|
||||
self.$emit('saveBlockEvent');
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
let message = self.$filters.translate(error.response.data.message);
|
||||
eventBus.$emit('publishermessage', message);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@@ -49,21 +49,12 @@ const determiner = {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/*
|
||||
video: function(block,lines,firstChar,secondChar,thirdChar){
|
||||
if( (firstChar == '!' && secondChar == '[' && lines[0].indexOf('.youtube') != -1) || (firstChar == '[' && secondChar == '!' && lines[0].indexOf('.youtube') != -1) )
|
||||
{
|
||||
return "video-component";
|
||||
}
|
||||
return false;
|
||||
},
|
||||
*/
|
||||
image: function(block,lines,firstChar,secondChar,thirdChar){
|
||||
if( (firstChar == '!' && secondChar == '[' ) || (firstChar == '[' && secondChar == '!' && thirdChar == '[') )
|
||||
{
|
||||
if(block.indexOf("-video") != -1)
|
||||
{
|
||||
return "video-component";
|
||||
return "youtube-component";
|
||||
}
|
||||
return "image-component";
|
||||
}
|
||||
@@ -76,6 +67,13 @@ const determiner = {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
video: function(block,lines,firstChar,secondChar,thirdChar){
|
||||
if (lines[0].startsWith('[:video'))
|
||||
{
|
||||
return "video-component";
|
||||
}
|
||||
return false;
|
||||
},
|
||||
code: function(block,lines,firstChar,secondChar,thirdChar){
|
||||
if( firstChar == '`' && secondChar == '`' && thirdChar == '`')
|
||||
{
|
||||
@@ -115,22 +113,23 @@ const bloxFormats = {
|
||||
quote: { label: '<svg class="icon icon-quotes-left"><use xlink:href="#icon-quotes-left"></use></svg>', title: 'Quote', component: 'quote-component' },
|
||||
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-play"><use xlink:href="#icon-play"></use></svg>', title: 'Video', component: 'video-component' },
|
||||
video: { label: '<svg class="icon icon-film"><use xlink:href="#icon-film"></use></svg>', title: 'Video', component: 'video-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' },
|
||||
definition: { label: '<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>', title: 'Definition List', component: 'definition-component' },
|
||||
code: { label: '<svg class="icon icon-embed"><use xlink:href="#icon-embed"></use></svg>', title: 'Code', component: 'code-component' },
|
||||
shortcode: { label: '<svg class="icon icon-square-brackets"><use xlink:href="#icon-square-brackets"></use></svg>', title: 'Shortcode', component: 'shortcode-component' },
|
||||
youtube: { label: '<svg class="icon icon-play"><use xlink:href="#icon-play"></use></svg>', title: 'YouTube', component: 'youtube-component' },
|
||||
};
|
||||
|
||||
const formatConfig = data.settings.formats;
|
||||
const activeFormats = [];
|
||||
const activeFormats = {};
|
||||
|
||||
for(var i = 0; i < formatConfig.length; i++)
|
||||
for (const format in bloxFormats)
|
||||
{
|
||||
if(bloxFormats[formatConfig[i]] !== undefined)
|
||||
{
|
||||
activeFormats.push(bloxFormats[formatConfig[i]]);
|
||||
}
|
||||
}
|
||||
if (formatConfig.includes(format))
|
||||
{
|
||||
activeFormats[format] = bloxFormats[format];
|
||||
}
|
||||
}
|
@@ -173,7 +173,7 @@ bloxeditor.component('content-block', {
|
||||
|
||||
componentType: false,
|
||||
updatedmarkdown: false,
|
||||
formats: bloxFormats,
|
||||
formats: activeFormats,
|
||||
hasUnsafedContent: false,
|
||||
countUpdates: 0
|
||||
}
|
||||
@@ -269,7 +269,6 @@ bloxeditor.component('content-block', {
|
||||
|
||||
let markdown = this.element.markdown;
|
||||
let lines = markdown.split("\n");
|
||||
let blockType = 'markdown-component';
|
||||
|
||||
for (var method in determiner)
|
||||
{
|
||||
@@ -277,11 +276,11 @@ bloxeditor.component('content-block', {
|
||||
|
||||
if(specialBlock)
|
||||
{
|
||||
blockType = specialBlock;
|
||||
return specialBlock;
|
||||
}
|
||||
}
|
||||
|
||||
return blockType;
|
||||
return 'markdown-component';
|
||||
},
|
||||
updateMarkdownFunction(value)
|
||||
{
|
||||
@@ -433,7 +432,7 @@ bloxeditor.component('new-block',{
|
||||
`,
|
||||
data: function () {
|
||||
return {
|
||||
formats: bloxFormats,
|
||||
formats: activeFormats,
|
||||
componentType: false,
|
||||
disabled: false,
|
||||
newblockmarkdown: '',
|
||||
|
@@ -252,12 +252,12 @@ const medialib = {
|
||||
if(this.parentcomponent == 'files')
|
||||
{
|
||||
this.showFiles();
|
||||
this.active = 'pageFiles';
|
||||
/* this.active = 'pageFiles'; */
|
||||
}
|
||||
if(this.parentcomponent == 'images')
|
||||
{
|
||||
this.showImages();
|
||||
this.active = 'pageImages';
|
||||
/* this.active = 'pageImages'; */
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@@ -42,6 +42,10 @@
|
||||
<title>{{ translate('VIDEO') }}</title>
|
||||
<path d="M30.662 5.003c-4.488-0.645-9.448-1.003-14.662-1.003s-10.174 0.358-14.662 1.003c-0.86 3.366-1.338 7.086-1.338 10.997s0.477 7.63 1.338 10.997c4.489 0.645 9.448 1.003 14.662 1.003s10.174-0.358 14.662-1.003c0.86-3.366 1.338-7.086 1.338-10.997s-0.477-7.63-1.338-10.997zM12 22v-12l10 6-10 6z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-film" viewBox="0 0 32 32">
|
||||
<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-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: 20 KiB |
@@ -132,6 +132,7 @@ fieldsetwriting:
|
||||
'definition': 'definition list'
|
||||
'code': 'code'
|
||||
'shortcode': 'shortcode'
|
||||
'youtube': 'YouTube (deprecated)'
|
||||
headlineanchors:
|
||||
type: checkbox
|
||||
label: 'Headline anchors'
|
||||
|
@@ -33,6 +33,7 @@ use Typemill\Middleware\FlashMessages;
|
||||
use Typemill\Middleware\AssetMiddleware;
|
||||
use Typemill\Middleware\SecurityMiddleware;
|
||||
use Typemill\Middleware\CustomHeadersMiddleware;
|
||||
use Typemill\Extensions\MediaExtension;
|
||||
use Typemill\Extensions\TwigCsrfExtension;
|
||||
use Typemill\Extensions\TwigUrlExtension;
|
||||
use Typemill\Extensions\TwigUserExtension;
|
||||
@@ -222,6 +223,9 @@ if(isset($updateSettings))
|
||||
$settingsModel->updateSettings($pluginSettings, 'plugins');
|
||||
}
|
||||
|
||||
# add media extension to integrate video/audio with shortcodes
|
||||
$dispatcher->addSubscriber(new MediaExtension($settings['rootPath'], $urlinfo['baseurl']));
|
||||
|
||||
# add final settings to the container
|
||||
$container->set('settings', function() use ($settings){ return $settings; });
|
||||
|
||||
|
@@ -140,7 +140,7 @@ img, figure,figure img{
|
||||
height: auto;
|
||||
aspect-ratio: attr(width) / attr(height);
|
||||
}
|
||||
figure{
|
||||
figure, video, audio{
|
||||
display: table;
|
||||
margin: 2em auto;
|
||||
padding: 0;
|
||||
|
Reference in New Issue
Block a user