feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
(function ($) {
|
||||
// Add an option for your plugin.
|
||||
// $.FroalaEditor.DEFAULTS = $.extend($.FroalaEditor.DEFAULTS, {
|
||||
// myOption: false
|
||||
// });
|
||||
|
||||
$.FroalaEditor.PLUGINS.figures = function (editor) {
|
||||
|
||||
/**
|
||||
* Insert UI Blocks
|
||||
*/
|
||||
function insertElement($el) {
|
||||
var html = $('<div />').append($el.clone()).remove().html()
|
||||
|
||||
// Make sure we have focus.
|
||||
editor.events.focus(true)
|
||||
editor.selection.restore()
|
||||
|
||||
editor.html.insert(html)
|
||||
editor.html.cleanEmptyTags()
|
||||
|
||||
// Clean up wrapping paragraphs or empty paragraphs
|
||||
$('figure', editor.$el).each(function() {
|
||||
var $this = $(this),
|
||||
$parent = $this.parent('p'),
|
||||
$next = $this.next('p')
|
||||
|
||||
// If block is inserted to a paragraph, insert it afterwards.
|
||||
if (!!$parent.length) {
|
||||
$this.insertAfter($parent)
|
||||
}
|
||||
|
||||
// Inserting a figure tag will put an empty paragraph tag
|
||||
// directly after it, strip these instances out
|
||||
if (!!$next.length && $.trim($next.text()).length == 0) {
|
||||
$next.remove()
|
||||
}
|
||||
})
|
||||
|
||||
editor.undo.saveStep()
|
||||
}
|
||||
|
||||
function _makeUiBlockElement() {
|
||||
var $node = $('<figure contenteditable="false" tabindex="0" data-ui-block="true"> </figure>')
|
||||
|
||||
$node.get(0).contentEditable = false
|
||||
|
||||
return $node
|
||||
}
|
||||
|
||||
function insertVideo(url, text) {
|
||||
var $node = _makeUiBlockElement()
|
||||
|
||||
$node.attr('data-video', url)
|
||||
$node.attr('data-label', text)
|
||||
|
||||
insertElement($node)
|
||||
}
|
||||
|
||||
function insertAudio(url, text) {
|
||||
var $node = _makeUiBlockElement()
|
||||
|
||||
$node.attr('data-audio', url)
|
||||
$node.attr('data-label', text)
|
||||
|
||||
insertElement($node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Init UI Blocks
|
||||
*/
|
||||
function _initUiBlocks () {
|
||||
$('[data-video], [data-audio]', editor.$el).each(function() {
|
||||
$(this)
|
||||
.addClass('fr-draggable')
|
||||
.attr({
|
||||
'data-ui-block': 'true',
|
||||
'draggable': 'true',
|
||||
'tabindex': '0'
|
||||
})
|
||||
.html(' ')
|
||||
|
||||
this.contentEditable = false
|
||||
})
|
||||
}
|
||||
|
||||
function _handleUiBlocksKeydown(ev) {
|
||||
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp' || ev.key === 'Backspace' || ev.key === 'Delete') {
|
||||
var $block = $(editor.selection.element())
|
||||
if ($block.is('br')) {
|
||||
$block = $block.parent()
|
||||
}
|
||||
|
||||
if (!!$block.length) {
|
||||
switch (ev.key) {
|
||||
case 'ArrowUp':
|
||||
_handleUiBlockCaretIn($block.prev())
|
||||
break
|
||||
case 'ArrowDown':
|
||||
_handleUiBlockCaretIn($block.next())
|
||||
break
|
||||
case 'Delete':
|
||||
_handleUiBlockCaretClearEmpty($block.next(), $block)
|
||||
break
|
||||
case 'Backspace':
|
||||
_handleUiBlockCaretClearEmpty($block.prev(), $block)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _handleUiBlockCaretClearEmpty($block, $p) {
|
||||
if ($block.attr('data-ui-block') !== undefined && $.trim($p.text()).length == 0) {
|
||||
$p.remove()
|
||||
_handleUiBlockCaretIn($block)
|
||||
editor.undo.saveStep()
|
||||
}
|
||||
}
|
||||
|
||||
function _handleUiBlockCaretIn($block) {
|
||||
if ($block.attr('data-ui-block') !== undefined) {
|
||||
$block.focus()
|
||||
editor.selection.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function _uiBlockKeyDown(ev, block) {
|
||||
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp' || ev.key === 'Enter' || ev.key === 'Backspace' || ev.key === 'Delete') {
|
||||
switch (ev.key) {
|
||||
case 'ArrowDown':
|
||||
_focusUiBlockOrText($(block).next(), true)
|
||||
break
|
||||
case 'ArrowUp':
|
||||
_focusUiBlockOrText($(block).prev(), false)
|
||||
break
|
||||
case 'Enter':
|
||||
var $paragraph = $('<p><br/></p>')
|
||||
$paragraph.insertAfter(block)
|
||||
editor.selection.setAfter(block)
|
||||
editor.selection.restore()
|
||||
editor.undo.saveStep()
|
||||
break
|
||||
case 'Backspace':
|
||||
case 'Delete':
|
||||
var $nextFocus = $(block).next(),
|
||||
gotoStart = true
|
||||
|
||||
if ($nextFocus.length == 0) {
|
||||
$nextFocus = $(block).prev()
|
||||
gotoStart = false
|
||||
}
|
||||
|
||||
_focusUiBlockOrText($nextFocus, gotoStart)
|
||||
|
||||
$(block).remove()
|
||||
editor.undo.saveStep()
|
||||
break
|
||||
}
|
||||
|
||||
ev.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
function _focusUiBlockOrText($block, gotoStart) {
|
||||
if (!!$block.length) {
|
||||
if (!_handleUiBlockCaretIn($block)) {
|
||||
if (gotoStart) {
|
||||
editor.selection.setAtStart($block.get(0))
|
||||
editor.selection.restore()
|
||||
}
|
||||
else {
|
||||
editor.selection.setAtEnd($block.get(0))
|
||||
editor.selection.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keydown
|
||||
*/
|
||||
function _onKeydown (ev) {
|
||||
_handleUiBlocksKeydown(ev)
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function _onFigureKeydown(ev) {
|
||||
if (ev.target && $(ev.target).attr('data-ui-block') !== undefined) {
|
||||
_uiBlockKeyDown(ev, ev.target)
|
||||
}
|
||||
|
||||
if (ev.isDefaultPrevented()) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync
|
||||
*/
|
||||
function _onSync(html) {
|
||||
var $domTree = $('<div>' + html + '</div>')
|
||||
|
||||
$domTree.find('[data-video], [data-audio]').each(function(){
|
||||
$(this)
|
||||
.removeAttr('contenteditable data-ui-block tabindex draggable')
|
||||
.removeClass('fr-draggable fr-dragging')
|
||||
})
|
||||
|
||||
return $domTree.html()
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
function _init () {
|
||||
editor.events.on('initialized', _initUiBlocks)
|
||||
|
||||
editor.events.on('html.set', _initUiBlocks)
|
||||
|
||||
editor.events.on('html.get', _onSync)
|
||||
|
||||
editor.events.on('keydown', _onKeydown)
|
||||
|
||||
editor.events.on('destroy', _destroy, true)
|
||||
|
||||
editor.$el.on('keydown', 'figure', _onFigureKeydown)
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy.
|
||||
*/
|
||||
function _destroy () {
|
||||
editor.$el.off('keydown', 'figure', _onFigureKeydown)
|
||||
}
|
||||
|
||||
return {
|
||||
_init: _init,
|
||||
insert: insertElement,
|
||||
insertVideo: insertVideo,
|
||||
insertAudio: insertAudio
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,287 @@
|
||||
(function ($) {
|
||||
|
||||
$.FroalaEditor.PLUGINS.mediaManager = function (editor) {
|
||||
|
||||
function onInsertFile() {
|
||||
new $.wn.mediaManager.popup({
|
||||
alias: 'ocmediamanager',
|
||||
cropAndInsertButton: false,
|
||||
onInsert: function(items) {
|
||||
if (!items.length) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_file_empty_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
if (items.length > 1) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_file_single_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
var link,
|
||||
text = editor.selection.text(),
|
||||
textIsEmpty = $.trim(text) === ''
|
||||
|
||||
for (var i=0, len=items.length; i<len; i++) {
|
||||
var text = textIsEmpty ? items[i].title : text
|
||||
|
||||
link = items[i].publicUrl
|
||||
}
|
||||
|
||||
// Focus in the editor.
|
||||
editor.events.focus(true);
|
||||
editor.selection.restore();
|
||||
|
||||
// Insert the link.
|
||||
editor.html.insert('<a href="' + link + '" id="fr-inserted-file" class="fr-file">' + text + '</a>');
|
||||
|
||||
// Get the file.
|
||||
var $file = editor.$el.find('#fr-inserted-file');
|
||||
$file.removeAttr('id');
|
||||
|
||||
editor.undo.saveStep()
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onInsertImage() {
|
||||
var $currentImage = editor.image.get(),
|
||||
selection = editor.selection.get(),
|
||||
range = editor.selection.ranges(0);
|
||||
|
||||
new $.wn.mediaManager.popup({
|
||||
alias: 'ocmediamanager',
|
||||
cropAndInsertButton: true,
|
||||
onInsert: function(items) {
|
||||
editor.selection.clear();
|
||||
selection.addRange(range);
|
||||
|
||||
if (!items.length) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_image_empty_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
var imagesInserted = 0
|
||||
|
||||
for (var i=0, len=items.length; i<len; i++) {
|
||||
if (items[i].documentType !== 'image') {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_image_invalid_insert', 'The file "'+items[i].title+'" is not an image.'))
|
||||
continue
|
||||
}
|
||||
|
||||
editor.image.insert(items[i].publicUrl, false, {}, $currentImage)
|
||||
imagesInserted++
|
||||
|
||||
if (imagesInserted == 1) {
|
||||
$currentImage = null
|
||||
}
|
||||
}
|
||||
|
||||
if (imagesInserted !== 0) {
|
||||
this.hide()
|
||||
editor.undo.saveStep()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onInsertVideo() {
|
||||
new $.wn.mediaManager.popup({
|
||||
alias: 'ocmediamanager',
|
||||
cropAndInsertButton: false,
|
||||
onInsert: function(items) {
|
||||
if (!items.length) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_video_empty_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
if (items.length > 1) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_file_single_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
var item = items[0]
|
||||
|
||||
if (item.documentType !== 'video') {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_video_invalid_insert', 'The file "'+item.title+'" is not a video.'))
|
||||
return
|
||||
}
|
||||
|
||||
var $richEditorNode = editor.$el.closest('[data-control="richeditor"]')
|
||||
|
||||
$richEditorNode.richEditor('insertVideo', item.publicUrl, item.title)
|
||||
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onInsertAudio() {
|
||||
new $.wn.mediaManager.popup({
|
||||
alias: 'ocmediamanager',
|
||||
cropAndInsertButton: false,
|
||||
onInsert: function(items) {
|
||||
if (!items.length) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_audio_empty_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
if (items.length > 1) {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_file_single_insert'))
|
||||
return
|
||||
}
|
||||
|
||||
var item = items[0]
|
||||
|
||||
if (item.documentType !== 'audio') {
|
||||
$.wn.alert($.wn.lang.get('mediamanager.invalid_audio_invalid_insert', 'The file "'+item.title+'" is not an audio file.'))
|
||||
return
|
||||
}
|
||||
|
||||
var $richEditorNode = editor.$el.closest('[data-control="richeditor"]')
|
||||
|
||||
$richEditorNode.richEditor('insertAudio', item.publicUrl, item.title)
|
||||
|
||||
this.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function _insertVideoFallback(link) {
|
||||
var $richEditorNode = editor.$el.closest('[data-control="richeditor"]')
|
||||
|
||||
var title = link.substring(link.lastIndexOf('/') + 1)
|
||||
|
||||
$richEditorNode.richEditor('insertVideo', link, title)
|
||||
|
||||
editor.popups.hide('video.insert')
|
||||
}
|
||||
|
||||
function _insertAudioFallback(link) {
|
||||
var $richEditorNode = editor.$el.closest('[data-control="richeditor"]')
|
||||
|
||||
var title = link.substring(link.lastIndexOf('/') + 1)
|
||||
|
||||
$richEditorNode.richEditor('insertAudio', link, title)
|
||||
|
||||
editor.popups.hide('audio.insert')
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
function _init () {
|
||||
editor.events.on('destroy', _destroy, true)
|
||||
|
||||
editor.events.on('video.linkError', _insertVideoFallback)
|
||||
|
||||
editor.events.on('audio.linkError', _insertAudioFallback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy.
|
||||
*/
|
||||
function _destroy () {
|
||||
}
|
||||
|
||||
// Expose public methods. If _init is not public then the plugin won't be initialized.
|
||||
// Public method can be accessed through the editor API:
|
||||
// $('.selector').froalaEditor('mediaManager.publicMethod');
|
||||
return {
|
||||
_init: _init,
|
||||
insertFile: onInsertFile,
|
||||
insertImage: onInsertImage,
|
||||
insertVideo: onInsertVideo,
|
||||
insertAudio: onInsertAudio
|
||||
}
|
||||
}
|
||||
|
||||
if (!$.FE.PLUGINS.link || !$.FE.PLUGINS.file || !$.FE.PLUGINS.image || !$.FE.PLUGINS.video) {
|
||||
throw new Error('Media manager plugin requires link, file, image and video plugin.');
|
||||
}
|
||||
|
||||
//
|
||||
// Image
|
||||
//
|
||||
|
||||
$.FE.DEFAULTS.imageInsertButtons.push('mmImageManager');
|
||||
|
||||
$.FE.RegisterCommand('mmImageManager', {
|
||||
title: 'Browse',
|
||||
undo: false,
|
||||
focus: false,
|
||||
callback: function () {
|
||||
this.mediaManager.insertImage();
|
||||
},
|
||||
plugin: 'mediaManager'
|
||||
})
|
||||
|
||||
// Add the font size icon.
|
||||
$.FE.DefineIcon('mmImageManager', {
|
||||
NAME: 'folder'
|
||||
});
|
||||
|
||||
//
|
||||
// File
|
||||
//
|
||||
|
||||
$.FE.DEFAULTS.fileInsertButtons.push('mmFileManager');
|
||||
|
||||
$.FE.RegisterCommand('mmFileManager', {
|
||||
title: 'Browse',
|
||||
undo: false,
|
||||
focus: false,
|
||||
callback: function () {
|
||||
this.mediaManager.insertFile();
|
||||
},
|
||||
plugin: 'mediaManager'
|
||||
})
|
||||
|
||||
// Add the font size icon.
|
||||
$.FE.DefineIcon('mmFileManager', {
|
||||
NAME: 'folder'
|
||||
});
|
||||
|
||||
//
|
||||
// Video
|
||||
//
|
||||
|
||||
$.FE.DEFAULTS.videoInsertButtons.push('mmVideoManager');
|
||||
|
||||
$.FE.RegisterCommand('mmVideoManager', {
|
||||
title: 'Browse',
|
||||
undo: false,
|
||||
focus: false,
|
||||
callback: function () {
|
||||
this.mediaManager.insertVideo();
|
||||
},
|
||||
plugin: 'mediaManager'
|
||||
})
|
||||
|
||||
// Add the font size icon.
|
||||
$.FE.DefineIcon('mmVideoManager', {
|
||||
NAME: 'folder'
|
||||
});
|
||||
|
||||
//
|
||||
// Audio
|
||||
//
|
||||
|
||||
$.FE.DEFAULTS.audioInsertButtons.push('mmAudioManager');
|
||||
|
||||
$.FE.RegisterCommand('mmAudioManager', {
|
||||
title: 'Browse',
|
||||
undo: false,
|
||||
focus: false,
|
||||
callback: function () {
|
||||
this.mediaManager.insertAudio();
|
||||
},
|
||||
plugin: 'mediaManager'
|
||||
})
|
||||
|
||||
// Add the font size icon.
|
||||
$.FE.DefineIcon('mmAudioManager', {
|
||||
NAME: 'folder'
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
@@ -0,0 +1,109 @@
|
||||
//
|
||||
// Page links
|
||||
//
|
||||
|
||||
var richeditorPageLinksPlugin
|
||||
|
||||
function richeditorPageLinksSelectPage($form) {
|
||||
richeditorPageLinksPlugin.setLinkValueFromPopup($form)
|
||||
}
|
||||
|
||||
$.FroalaEditor.DEFAULTS = $.extend($.FroalaEditor.DEFAULTS, {
|
||||
pageLinksHandler: 'onLoadPageLinksForm'
|
||||
});
|
||||
|
||||
$.FroalaEditor.DEFAULTS.key = 'JA6B2B5A1qB1F1F4D3I1A15A11D3E6B5dVh1VCQWa1EOQFe1NCb1==';
|
||||
|
||||
(function ($) {
|
||||
$.FroalaEditor.PLUGINS.pageLinks = function (editor) {
|
||||
|
||||
function setLinkValueFromPopup($form) {
|
||||
var $select = $('select[name=pagelink]', $form)
|
||||
|
||||
var link = {
|
||||
text: $('option:selected', $select).text().trim(),
|
||||
href: $select.val()
|
||||
}
|
||||
|
||||
// Wait for popup to close
|
||||
setTimeout(function() {
|
||||
editor.popups.show('link.insert')
|
||||
|
||||
setLinkValue(link)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function setLinkValue(link) {
|
||||
var $popup = editor.popups.get('link.insert');
|
||||
var text_inputs = $popup.find('input.fr-link-attr[type="text"]');
|
||||
var check_inputs = $popup.find('input.fr-link-attr[type="checkbox"]');
|
||||
|
||||
var $input;
|
||||
var i;
|
||||
for (i = 0; i < text_inputs.length; i++) {
|
||||
$input = $(text_inputs[i]);
|
||||
var name = $input.attr('name');
|
||||
var value = link[name];
|
||||
|
||||
// Only change the text of the link to be inserted if it has not already been set
|
||||
if (name === 'text') {
|
||||
if ($input.val().length === 0) {
|
||||
$input.val(value);
|
||||
}
|
||||
} else {
|
||||
$input.val(value);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < check_inputs.length; i++) {
|
||||
$input = $(check_inputs[i]);
|
||||
$input.prop('checked', $input.data('checked') == link[$input.attr('name')]);
|
||||
}
|
||||
|
||||
// Restore selection, so that the link gets inserted properly.
|
||||
editor.selection.restore();
|
||||
}
|
||||
|
||||
function insertLink() {
|
||||
richeditorPageLinksPlugin = this
|
||||
|
||||
editor.$el.popup({
|
||||
handler: editor.opts.pageLinksHandler
|
||||
}).one('shown.oc.popup.pageLinks', function () {
|
||||
// Save the current selection so it can be restored after popup is closed.
|
||||
editor.selection.save()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
function _init () {
|
||||
}
|
||||
|
||||
return {
|
||||
_init: _init,
|
||||
setLinkValueFromPopup: setLinkValueFromPopup,
|
||||
setLinkValue: setLinkValue,
|
||||
insertLink: insertLink
|
||||
}
|
||||
}
|
||||
|
||||
$.FE.DEFAULTS.linkInsertButtons = ['linkBack', '|', 'linkPageLinks']
|
||||
|
||||
$.FE.RegisterCommand('linkPageLinks', {
|
||||
title: 'Choose Link',
|
||||
undo: false,
|
||||
focus: false,
|
||||
callback: function () {
|
||||
this.pageLinks.insertLink()
|
||||
},
|
||||
plugin: 'pageLinks'
|
||||
})
|
||||
|
||||
// Add the font size icon.
|
||||
$.FE.DefineIcon('linkPageLinks', {
|
||||
NAME: 'search'
|
||||
});
|
||||
|
||||
})(jQuery);
|
||||
Reference in New Issue
Block a user