feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
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:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,99 @@
import Sortable from 'sortablejs';
/*
* List widget drag-and-drop reordering.
*
* Additive enhancement on top of the existing list widget. When a list is rendered with
* `data-sortable="true"`, SortableJS is initialised on its <tbody> using the per-row drag
* handle cell. On drop, the record ids in their new DOM order are posted to the list's reorder
* handler, which assigns the sort order values server-side (by position) and re-renders.
*/
(function ($) {
"use strict";
function collectIds(tbody) {
return Array.prototype.map.call(
tbody.querySelectorAll('tr[data-record-id]'),
function (tr) {
return tr.getAttribute('data-record-id');
}
);
}
function initList(listEl) {
var tbody = listEl.querySelector('tbody');
if (!tbody || tbody.wnListSortable) {
return;
}
var handler = listEl.getAttribute('data-reorder-handler');
if (!handler) {
return;
}
var $list = $(listEl);
// SortableJS dispatches a native "change" event on the list root (the tbody) while
// an item is being dragged. Stop it bubbling to the surrounding form's change monitor
// so reordering — which persists immediately — does not flag the form as having
// unsaved changes. Real form-field changes (target = input/select/textarea) are left
// untouched.
tbody.addEventListener('change', function (event) {
if (event.target === tbody) {
event.stopPropagation();
}
});
var reorderInFlight = false;
tbody.wnListSortable = Sortable.create(tbody, {
handle: '.list-cell-sort-handle',
draggable: 'tr',
filter: '.no-data',
animation: 150,
ghostClass: 'list-sortable-ghost',
chosenClass: 'list-sortable-chosen',
onEnd: function (event) {
// Nothing changed if the row was dropped back in its original position.
if (event.oldIndex === event.newIndex) {
return;
}
// Ignore further drops until the current reorder has been persisted and the
// list re-rendered, so overlapping requests can't race and persist a stale order.
if (reorderInFlight) {
return;
}
reorderInFlight = true;
// The request is fired programmatically (not from a [data-request] element),
// so show the stripe load indicator manually for feedback.
var indicator = ($.wn && $.wn.stripeLoadIndicator) || ($.oc && $.oc.stripeLoadIndicator);
if (indicator) {
indicator.show();
}
// Only the record ids (in their new order) are sent; the server assigns the
// sort order values by position.
$list.request(handler, {
data: { record_ids: collectIds(tbody) }
}).always(function () {
reorderInFlight = false;
if (indicator) {
indicator.hide();
}
});
}
});
}
function initAll() {
var lists = document.querySelectorAll('[data-control="listwidget"][data-sortable="true"]');
Array.prototype.forEach.call(lists, initList);
}
$(document).ready(initAll);
// Re-initialise after the list partial is replaced by an AJAX update (e.g. onRefresh).
$(document).on('render', initAll);
})(window.jQuery);

View File

@@ -0,0 +1,166 @@
/*
* List Widget
*
* Dependences:
* - Row Link Plugin (system/assets/ui/js/list.rowlink.js)
*/
+function ($) { "use strict";
var ListWidget = function (element, options) {
var $el = this.$el = $(element);
this.options = options || {};
var scrollClassContainer = options.scrollClassContainer !== undefined
? options.scrollClassContainer
: $el.parent()
$el.dragScroll({
scrollClassContainer: scrollClassContainer,
scrollSelector: 'thead',
dragSelector: 'thead'
})
this.update()
}
ListWidget.DEFAULTS = {
}
ListWidget.prototype.update = function() {
var
list = this.$el,
head = $('thead', list),
body = $('tbody', list),
foot = $('tfoot', list)
/*
* Bind check boxes
*/
$('.list-checkbox input[type="checkbox"]', body).each(function(){
var $el = $(this)
if ($el.is(':checked'))
$el.closest('tr').addClass('active')
})
head.on('change', '.list-checkbox input[type="checkbox"]', function(){
var $el = $(this),
checked = $el.is(':checked')
$('.list-checkbox input[type="checkbox"]', body).prop('checked', checked)
if (checked)
$('tr', body).addClass('active')
else
$('tr', body).removeClass('active')
})
body.on('change', '.list-checkbox input[type="checkbox"]', function(){
var $el = $(this),
checked = $el.is(':checked')
if (checked) {
$el.closest('tr').addClass('active')
}
else {
$('.list-checkbox input[type="checkbox"]', head).prop('checked', false)
$el.closest('tr').removeClass('active')
}
})
this.lastChecked = null
body.on('click', '.list-checkbox input[type="checkbox"]', (e) => {
const current = e.currentTarget
if (this.lastChecked && e.shiftKey) {
const checkboxes = $('.list-checkbox input[type="checkbox"]', body)
const start = checkboxes.index(current)
const end = checkboxes.index(this.lastChecked)
checkboxes
.slice(Math.min(start, end), Math.max(start, end) + 1)
.each(function () {
$(this).prop('checked', current.checked).trigger('change')
})
}
this.lastChecked = current
})
}
ListWidget.prototype.getChecked = function() {
var
list = this.$el,
body = $('tbody', list)
return $('.list-checkbox input[type="checkbox"]', body).map(function(){
var $el = $(this)
if ($el.is(':checked'))
return $el.val()
}).get();
}
ListWidget.prototype.toggleChecked = function(el) {
var $checkbox = $('.list-checkbox input[type="checkbox"]', $(el).closest('tr'))
$checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change')
}
// LIST WIDGET PLUGIN DEFINITION
// ============================
var old = $.fn.listWidget
$.fn.listWidget = function (option) {
var args = Array.prototype.slice.call(arguments, 1), result
this.each(function () {
var $this = $(this)
var data = $this.data('oc.listwidget')
var options = $.extend({}, ListWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.listwidget', (data = new ListWidget(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
$.fn.listWidget.Constructor = ListWidget
// LIST WIDGET NO CONFLICT
// =================
$.fn.listWidget.noConflict = function () {
$.fn.listWidget = old
return this
}
// LIST WIDGET HELPERS
// =================
if ($.wn === undefined)
$.wn = {}
if ($.oc === undefined)
$.oc = $.wn
$.wn.listToggleChecked = function(el) {
$(el)
.closest('[data-control="listwidget"]')
.listWidget('toggleChecked', el)
}
$.wn.listGetChecked = function(el) {
return $(el)
.closest('[data-control="listwidget"]')
.listWidget('getChecked')
}
// LIST WIDGET DATA-API
// ==============
$(document).render(function(){
$('[data-control="listwidget"]').listWidget();
})
}(window.jQuery);