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

View File

@@ -0,0 +1,54 @@
<?php namespace Cms\Console;
use System\Console\BaseScaffoldCommand;
class CreateComponent extends BaseScaffoldCommand
{
/**
* The default command name for lazy loading.
*
* @var string|null
*/
protected static $defaultName = 'create:component';
/**
* The name and signature of this command.
*
* @var string
*/
protected $signature = 'create:component
{plugin : The name of the plugin. <info>(eg: Winter.Blog)</info>}
{component : The name of the component to generate. <info>(eg: Posts)</info>}
{--force : Overwrite existing files with generated files.}
{--uninspiring : Disable inspirational quotes}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Creates a new plugin component.';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Component';
/**
* @var string The argument that the generated class name comes from
*/
protected $nameFrom = 'component';
/**
* A mapping of stub to generated file.
*
* @var array
*/
protected $stubs = [
'scaffold/component/component.stub' => 'components/{{studly_name}}.php',
'scaffold/component/default.stub' => 'components/{{lower_name}}/default.htm',
];
}

View File

@@ -0,0 +1,217 @@
<?php namespace Cms\Console;
use InvalidArgumentException;
use System\Classes\Asset\PackageManager;
use Winter\Storm\Exception\SystemException;
use Winter\Storm\Scaffold\GeneratorCommand;
class CreateTheme extends GeneratorCommand
{
/**
* @var string|null The default command name for lazy loading.
*/
protected static $defaultName = 'create:theme';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'create:theme
{theme : The name of the theme to create. <info>(eg: MyTheme)</info>}
{scaffold? : The base theme scaffold to use <info>(eg: less, tailwind)</info>}
{--f|force : Overwrite existing files with generated files.}
{--uninspiring : Disable inspirational quotes}
';
/**
* @var string The console command description.
*/
protected $description = 'Creates a new theme.';
/**
* @var string The type of class being generated.
*/
protected $type = 'Theme';
/**
* @var string The argument that the generated class name comes from
*/
protected $nameFrom = 'theme';
/**
* @var string The scaffold that we are building
*/
protected string $scaffold;
/**
* @var array Available theme scaffolds and their types
*/
protected $themeScaffolds = [
'less' => [
'scaffold/theme/less/assets/js/app.stub' => 'assets/js/app.js',
'scaffold/theme/less/assets/less/theme.stub' => 'assets/less/theme.less',
'scaffold/theme/less/layouts/default.stub' => 'layouts/default.htm',
'scaffold/theme/less/pages/404.stub' => 'pages/404.htm',
'scaffold/theme/less/pages/error.stub' => 'pages/error.htm',
'scaffold/theme/less/pages/home.stub' => 'pages/home.htm',
'scaffold/theme/less/partials/meta/seo.stub' => 'partials/meta/seo.htm',
'scaffold/theme/less/partials/meta/styles.stub' => 'partials/meta/styles.htm',
'scaffold/theme/less/partials/site/header.stub' => 'partials/site/header.htm',
'scaffold/theme/less/partials/site/footer.stub' => 'partials/site/footer.htm',
'scaffold/theme/less/theme.stub' => 'theme.yaml',
'scaffold/theme/less/version.stub' => 'version.yaml',
],
'tailwind' => [
'scaffold/theme/tailwind/lang/en/lang.stub' => 'lang/en/lang.php',
'scaffold/theme/tailwind/layouts/default.stub' => 'layouts/default.htm',
'scaffold/theme/tailwind/pages/404.stub' => 'pages/404.htm',
'scaffold/theme/tailwind/pages/error.stub' => 'pages/error.htm',
'scaffold/theme/tailwind/pages/home.stub' => 'pages/home.htm',
'scaffold/theme/tailwind/partials/meta/seo.stub' => 'partials/meta/seo.htm',
'scaffold/theme/tailwind/partials/meta/styles.stub' => 'partials/meta/styles.htm',
'scaffold/theme/tailwind/partials/site/header.stub' => 'partials/site/header.htm',
'scaffold/theme/tailwind/partials/site/footer.stub' => 'partials/site/footer.htm',
'scaffold/theme/tailwind/.gitignore.stub' => '.gitignore',
'scaffold/theme/tailwind/README.stub' => 'README.md',
'scaffold/theme/tailwind/theme.stub' => 'theme.yaml',
'scaffold/theme/tailwind/version.stub' => 'version.yaml',
],
];
/**
* Get the desired class name from the input.
*/
protected function getNameInput(): string
{
return str_slug(parent::getNameInput());
}
/**
* Prepare variables for stubs.
*/
protected function prepareVars(): array
{
$this->scaffold = $this->argument('scaffold') ?? 'tailwind';
$validOptions = $this->suggestScaffoldValues();
if (!in_array($this->scaffold, $validOptions)) {
throw new InvalidArgumentException("$this->scaffold is not an available theme scaffold type (Available types: " . implode(', ', $validOptions) . ')');
}
$this->stubs = $this->themeScaffolds[$this->scaffold];
return [
'code' => $this->getNameInput(),
];
}
/**
* Auto suggest valid theme scaffold values
*/
public function suggestScaffoldValues(): array
{
return array_keys($this->themeScaffolds);
}
/**
* Get the plugin path from the input.
*/
protected function getDestinationPath(): string
{
return themes_path($this->getNameInput());
}
/**
* Make a single stub.
*
* @param string $stubName The source filename for the stub.
*/
public function makeStub($stubName)
{
if (!isset($this->stubs[$stubName])) {
return;
}
$sourceFile = $this->getSourcePath() . '/' . $stubName;
$destinationFile = $this->getDestinationForStub($stubName);
$destinationContent = $this->files->get($sourceFile);
/*
* Parse each variable in to the destination content and path
* @NOTE: CANNOT USE TWIG AS IT WOULD CONFLICT WITH THE TWIG TEMPLATES THEMSELVES
*/
foreach ($this->vars as $key => $var) {
$destinationContent = str_replace('{{' . $key . '}}', $var, $destinationContent);
$destinationFile = str_replace('{{' . $key . '}}', $var, $destinationFile);
}
$this->makeDirectory($destinationFile);
$this->files->put($destinationFile, $destinationContent);
}
public function makeStubs(): void
{
parent::makeStubs();
if ($this->scaffold === 'tailwind') {
// @TODO: allow support for mix here
$this->tailwindPostCreate('vite');
}
}
protected function tailwindPostCreate(string $processor): void
{
if ($this->call('npm:version', ['--silent' => true, '--compatible' => true]) !== 0) {
throw new SystemException(sprintf(
'NPM is not installed or is outdated, please ensure NPM >= v7.0 is available and then manually set up %s.',
$processor
));
}
$commands = [
// Set up the vite config files
$processor . ':create' => [
'message' => 'Generating ' . $processor . ' + tailwind config...',
'args' => [
'packageName' => 'theme-' . $this->getNameInput(),
'--no-interaction' => true,
'--force' => true,
'--silent' => true,
'--tailwind' => true
]
],
// Ensure all require packages are available for the new theme and add the new theme to our npm workspaces
$processor . ':install' => [
'message' => 'Installing NPM dependencies...',
'args' => [
'assetPackage' => ['theme-' . $this->getNameInput()],
'--no-interaction' => true,
'--silent' => false,
'--disable-tty' => true
]
],
// Run an initial compile to ensure styles are available for first load
$processor . ':compile' => [
'message' => 'Compiling your theme...',
'args' => [
'--package' => ['theme-' . $this->getNameInput()],
'--no-interaction' => true,
'--silent' => true,
]
]
];
foreach ($commands as $command => $data) {
$this->info($data['message']);
// Handle commands throwing errors
if ($this->call($command, $data['args']) !== 0) {
throw new SystemException(sprintf('Post create command `%s` failed, please review manually.', $command));
}
// Force PackageManger to reset available packages
if ($command === $processor . ':create') {
PackageManager::forgetInstance();
}
}
}
}

View File

@@ -0,0 +1,130 @@
<?php namespace Cms\Console;
use Cms\Classes\Theme;
use Cms\Classes\ThemeManager;
use File;
use System\Classes\UpdateManager;
use Winter\Storm\Console\Command;
/**
* Console command to install a new theme.
*
* This adds a new theme by requesting it from the Winter marketplace.
*
* @package winter\wn-cms-module
* @author Alexey Bobkov, Samuel Georges
*/
class ThemeInstall extends Command
{
/**
* The console command name.
* @var string
*/
protected $name = 'theme:install';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'theme:install
{name : The name of the theme. <info>(eg: AuthorName.ThemeName)</info>}
{dirName? : Destination directory name for the theme installation.}
';
/**
* The console command description.
* @var string
*/
protected $description = 'Install a theme from the Winter marketplace.';
/**
* Execute the console command.
* @return void
*/
public function handle()
{
$themeName = $this->argument('name');
$argDirName = $this->argument('dirName');
if ($argDirName && $themeName == $argDirName) {
$argDirName = null;
}
if ($argDirName) {
if (!Theme::isValidDirName($argDirName)) {
return $this->error('Invalid destination directory name.');
}
if (Theme::exists($argDirName)) {
return $this->error(sprintf('A theme named %s already exists.', $argDirName));
}
}
try {
$themeManager = ThemeManager::instance();
$updateManager = UpdateManager::instance();
$themeDetails = $updateManager->requestThemeDetails($themeName);
if ($themeManager->isInstalled($themeDetails['code'])) {
return $this->error(sprintf('The theme %s is already installed.', $themeDetails['code']));
}
if (Theme::exists($themeDetails['code'])) {
return $this->error(sprintf('A theme named %s already exists.', $themeDetails['code']));
}
$fields = ['Name', 'Description', 'Author', 'URL', ''];
$this->info(sprintf(
implode(': %s'.PHP_EOL, $fields),
$themeDetails['code'],
$themeDetails['description'],
$themeDetails['author'],
$themeDetails['product_url']
));
if (!$this->confirm('Do you wish to continue? [Y|n]', true)) {
return;
}
$this->info('Downloading theme...');
$updateManager->downloadTheme($themeDetails['code'], $themeDetails['hash']);
$this->info('Extracting theme...');
$updateManager->extractTheme($themeDetails['code'], $themeDetails['hash']);
$dirName = $this->themeCodeToDir($themeDetails['code']);
if ($argDirName) {
/*
* Move downloaded theme to a new directory.
* Basically we're renaming it.
*/
File::move(themes_path().'/'.$dirName, themes_path().'/'.$argDirName);
/*
* Let's make sure to unflag the 'old' theme as
* installed so it can be re-installed later.
*/
$themeManager->setUninstalled($themeDetails['code']);
$dirName = $argDirName;
}
$this->info(sprintf('The theme %s has been installed. (now %s)', $themeDetails['code'], $dirName));
} catch (\Throwable $ex) {
$this->error($ex->getMessage());
}
}
/**
* Theme code to dir.
*
* @param string $themeCode
* @return string
*/
protected function themeCodeToDir($themeCode)
{
return strtolower(str_replace('.', '-', $themeCode));
}
}

View File

@@ -0,0 +1,66 @@
<?php namespace Cms\Console;
use Cms\Classes\Theme;
use Cms\Classes\ThemeManager;
use System\Classes\UpdateManager;
use Winter\Storm\Console\Command;
/**
* Console command to list themes.
*
* This lists all the available themes in the system. It also shows the active theme.
*
* @package winter\wn-cms-module
* @author Alexey Bobkov, Samuel Georges
*/
class ThemeList extends Command
{
/**
* The console command name.
*/
protected $name = 'theme:list';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'theme:list
{--m|include-marketplace : Include downloadable themes from the Winter marketplace.}
';
/**
* The console command description.
*/
protected $description = 'List available themes.';
/**
* Execute the console command.
*/
public function handle()
{
$themeManager = ThemeManager::instance();
$updateManager = UpdateManager::instance();
$results = [];
foreach (Theme::all() as $theme) {
$results[] = [
'code' => $theme->getId(),
'is_active' => $theme->isActiveTheme() ? '<info>Yes</info>': '<fg=red>No</>',
'is_installed' => '<info>Yes</info>',
];
}
if ($this->option('include-marketplace')) {
// @TODO List everything in the marketplace - not just popular.
$popularThemes = $updateManager->requestPopularProducts('theme');
foreach ($popularThemes as $popularTheme) {
$results[] = [
'code' => $popularTheme['code'],
'is_active' => '<fg=red>No</>',
'is_installed' => $themeManager->isInstalled($popularTheme['code']) ? '<info>Yes</info>': '<fg=red>No</>',
];
}
}
$this->table(['Theme', 'Active', 'Installed'], $results);
}
}

View File

@@ -0,0 +1,70 @@
<?php namespace Cms\Console;
use Cms\Classes\Theme;
use Cms\Classes\ThemeManager;
use Exception;
use Winter\Storm\Console\Command;
/**
* Console command to remove a theme.
*
* This completely deletes an existing theme, including all files and directories.
*
* @package winter\wn-cms-module
* @author Alexey Bobkov, Samuel Georges
*/
class ThemeRemove extends Command
{
use \Illuminate\Console\ConfirmableTrait;
/**
* The console command name.
* @var string
*/
protected $name = 'theme:remove';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'theme:remove
{name : The name of the theme to delete. <info>(eg: mytheme)</info>}
{--f|force : Force the operation to run.}
';
/**
* The console command description.
* @var string
*/
protected $description = 'Delete an existing theme.';
/**
* Execute the console command.
* @return void
*/
public function handle()
{
$themeManager = ThemeManager::instance();
$themeName = $this->argument('name');
$themeExists = Theme::exists($themeName);
if (!$themeExists) {
$themeName = strtolower(str_replace('.', '-', $themeName));
$themeExists = Theme::exists($themeName);
}
if (!$themeExists) {
return $this->error(sprintf('The theme %s does not exist.', $themeName));
}
if (!$this->confirmToProceed(sprintf('This will DELETE theme "%s" from the filesystem and database.', $themeName))) {
return;
}
try {
$themeManager->deleteTheme($themeName);
$this->info(sprintf('The theme %s has been deleted.', $themeName));
} catch (Exception $ex) {
$this->error($ex->getMessage());
}
}
}

View File

@@ -0,0 +1,234 @@
<?php namespace Cms\Console;
use Cms\Classes\Theme;
use Event;
use Exception;
use Winter\Storm\Console\Command;
/**
* Console command to sync a theme between the DB and Filesystem layers.
*
* theme:sync name --paths=file/to/sync.md,other/file/to/sync.md --target=filesystem --force
*
* - name defaults to the currently active theme
* - --paths defaults to all paths within the theme, otherwise comma-separated list of paths relative to the theme directory
* - --target defaults to "filesystem", the source will whichever of filesystem vs database is not the target
* - --force bypasses the confirmation request
*
* @package winter\wn-cms-module
* @author Luke Towers
*/
class ThemeSync extends Command
{
use \Illuminate\Console\ConfirmableTrait;
/**
* The console command name.
* @var string
*/
protected $name = 'theme:sync';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'theme:sync
{name? : The name of the theme (directory name). Defaults to currently active theme.}
{--paths= : Comma-separated specific paths (relative to provided theme directory) to specificaly sync. Default is all paths. You may use regular expressions.}
{--target= : The target of the sync, the other will be used as the source. Defaults to "filesystem", can be "database"}
{--f|force : Force the operation to run.}
';
/**
* The console command description.
* @var string
*/
protected $description = 'Sync an existing theme between the DB and Filesystem layers';
/**
* @var \Cms\Classes\AutoDatasource The theme's AutoDatasource instance
*/
protected $datasource;
/**
* @var string The datasource key that the sync is targeting
*/
protected $target;
/**
* @var string The datasource key that the sync is sourcing from
*/
protected $source;
/**
* Execute the console command.
* @return void
*/
public function handle()
{
// Check to see if the application even uses a database
if (!$this->laravel->hasDatabase()) {
return $this->error("The application is not using a database.");
}
// Check to see if the DB layer is enabled
if (!Theme::databaseLayerEnabled()) {
return $this->error("cms.databaseTemplates is not enabled, enable it first and try again.");
}
// Check to see if the provided theme exists
$themeName = $this->argument('name') ?: Theme::getActiveThemeCode();
$themeExists = Theme::exists($themeName);
if (!$themeExists) {
$themeName = strtolower(str_replace('.', '-', $themeName));
$themeExists = Theme::exists($themeName);
}
if (!$themeExists) {
return $this->error(sprintf('The theme %s does not exist.', $themeName));
}
$theme = Theme::load($themeName);
$this->datasource = $theme->getDatasource();
// Get the target and source datasources
$availableSources = ['filesystem', 'database'];
$target = $this->option('target') ?: 'filesystem';
$source = ($target === 'filesystem') ? 'database' : 'filesystem';
if (!in_array($target, $availableSources)) {
return $this->error(sprintf("Provided --target of %s is invalid. Allowed: filesystem, database", $target));
}
$this->source = $source;
$this->target = $target;
// Get the theme paths, taking into account if the user has specified paths
$userPaths = $this->option('paths') ?: null;
$themePaths = array_keys($this->datasource->getSourcePaths($source));
if (!isset($userPaths)) {
$paths = $themePaths;
} else {
$paths = [];
$userPaths = array_map('trim', explode(',', $userPaths));
foreach ($userPaths as $userPath) {
foreach ($themePaths as $themePath) {
$pregMatch = '/^' . str_replace('/', '\/', $userPath) . '/i';
if ($userPath === $themePath || preg_match($pregMatch, $themePath)) {
$paths[] = $themePath;
}
}
}
}
// Determine valid paths based on the models made available for syncing
$validPaths = [];
/**
* @event system.console.theme.sync.getAvailableModelClasses
* Defines the Halcyon models to be made available to the `theme:sync` tool.
*
* Example usage:
*
* Event::listen('system.console.theme.sync.getAvailableModelClasses', function () {
* return [
* Meta::class,
* Page::class,
* Layout::class,
* Content::class,
* Partial::class,
* ];
* });
*
*/
$eventResults = Event::fire('system.console.theme.sync.getAvailableModelClasses');
$validModels = [];
foreach ($eventResults as $result) {
if (!is_array($result)) {
continue;
}
foreach ($result as $modelClass) {
$modelObj = new $modelClass;
if ($modelObj instanceof \Winter\Storm\Halcyon\Model) {
$validModels[] = $modelObj;
}
}
}
// Check each path and map it to a corresponding model
foreach ($paths as $path) {
foreach ($validModels as $model) {
if (
starts_with($path, $model->getObjectTypeDirName() . '/')
&& in_array(pathinfo($path, PATHINFO_EXTENSION), $model->getAllowedExtensions())
) {
$validPaths[$path] = get_class($model);
// Skip to the next path
continue 2;
}
}
}
if (count($validPaths) === 0) {
return $this->error(sprintf('No applicable paths found for %s.', $source));
}
// Confirm with the user
if (!$this->confirmToProceed(sprintf('This will OVERWRITE the %s provided paths in "themes/%s" on the %s with content from the %s', count($validPaths), $themeName, $target, $source), true)) {
return;
}
try {
$this->info('Syncing files, please wait...');
$progress = $this->output->createProgressBar(count($validPaths));
foreach ($validPaths as $path => $model) {
$entity = $this->getModelForPath($path, $model, $theme);
if (!isset($entity)) {
continue;
}
$this->datasource->pushToSource($entity, $target);
$progress->advance();
}
$progress->finish();
$this->info('');
$this->info(sprintf('The theme %s has been successfully synced from the %s to the %s.', $themeName, $source, $target));
}
catch (Exception $ex) {
$this->error($ex->getMessage());
}
}
/**
* Get the correct Halcyon model for the provided path from the source datasource and load the requested path data.
*
* @param string $path
* @param string $model
* @param \Cms\Classes\Theme $theme
* @return \Winter\Storm\Halcyon\Model
*/
protected function getModelForPath($path, $modelClass, $theme)
{
return $this->datasource->usingSource($this->source, function () use ($path, $modelClass, $theme) {
$modelObj = new $modelClass;
$entity = $modelClass::load(
$theme,
str_replace($modelObj->getObjectTypeDirName() . '/', '', $path)
);
if (!isset($entity)) {
return null;
}
return $entity;
});
}
}

View File

@@ -0,0 +1,66 @@
<?php namespace Cms\Console;
use Cms\Classes\Theme;
use Winter\Storm\Console\Command;
/**
* Console command to switch themes.
*
* This switches the active theme to another one, saved to the database.
*
* @package winter\wn-cms-module
* @author Alexey Bobkov, Samuel Georges
*/
class ThemeUse extends Command
{
use \Illuminate\Console\ConfirmableTrait;
/**
* The console command name.
* @var string
*/
protected $name = 'theme:use';
/**
* @var string The name and signature of this command.
*/
protected $signature = 'theme:use
{name : The name of the theme. (directory name).}
{--f|force : Force the operation to run.}
';
/**
* The console command description.
* @var string
*/
protected $description = 'Switch the active theme.';
/**
* Execute the console command.
* @return void
*/
public function handle()
{
if (!$this->confirmToProceed('Change the active theme?')) {
return;
}
$newThemeName = $this->argument('name');
$newTheme = Theme::load($newThemeName);
if (!$newTheme->exists($newThemeName)) {
return $this->error(sprintf('The theme %s does not exist.', $newThemeName));
}
if ($newTheme->isActiveTheme()) {
return $this->error(sprintf('%s is already the active theme.', $newTheme->getId()));
}
$activeTheme = Theme::getActiveTheme();
$from = $activeTheme ? $activeTheme->getId() : 'nothing';
$this->info(sprintf('Switching theme from %s to %s', $from, $newTheme->getId()));
Theme::setActiveTheme($newThemeName);
}
}

View File

@@ -0,0 +1,25 @@
<?php namespace {{studly_author}}\{{studly_plugin}}\Components;
use Cms\Classes\ComponentBase;
class {{studly_name}} extends ComponentBase
{
/**
* Gets the details for the component
*/
public function componentDetails()
{
return [
'name' => '{{name}} Component',
'description' => 'No description provided yet...'
];
}
/**
* Returns the properties provided by the component
*/
public function defineProperties()
{
return [];
}
}

View File

@@ -0,0 +1,3 @@
<p>This is the default markup for component {{name}}</p>
<small>You can delete this file if you want</small>

View File

@@ -0,0 +1,33 @@
/*
* Application
*/
(function($) {
"use strict";
jQuery(document).ready(function($) {
/*-------------------------------
WINTER CMS FLASH MESSAGE HANDLING
---------------------------------*/
$(document).on('ajaxSetup', function(event, context) {
// Enable AJAX handling of Flash messages on all AJAX requests
context.options.flash = true;
// Enable the StripeLoadIndicator on all AJAX requests
context.options.loading = $.oc.stripeLoadIndicator;
// Handle Flash Messages
context.options.handleFlashMessage = function(message, type) {
$.oc.flashMsg({ text: message, class: type });
};
// Handle Error Messages
context.options.handleErrorMessage = function(message) {
$.oc.flashMsg({ text: message, class: 'error' });
};
});
});
}(jQuery));
if (typeof(gtag) !== 'function') {
gtag = function() { console.log('GoogleAnalytics not present.'); }
}

View File

@@ -0,0 +1,5 @@
.content {
margin: 2em auto;
max-width: 1080px;
text-align: center;
}

View File

@@ -0,0 +1,7 @@
description = "Default layout"
==
{% partial "site/header" %}
{% page %}
{% partial "site/footer" %}

View File

@@ -0,0 +1,8 @@
title = "Page not found (404)"
url = "/404"
layout = "default"
==
<div class="content">
<h1>Page not found</h1>
<p>We're sorry, but the page you requested cannot be found.</p>
</div>

View File

@@ -0,0 +1,8 @@
title = "Error page (500)"
url = "/error"
layout = "default"
==
<div class="content">
<h1>Error</h1>
<p>We're sorry, but something went wrong and the page cannot be displayed.</p>
</div>

View File

@@ -0,0 +1,7 @@
title = "Home"
url = "/"
layout = "default"
==
<div class="content">
<h1>Home Page</h1>
</div>

View File

@@ -0,0 +1,11 @@
{% if this.theme.googleanalytics_id is not empty %}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ this.theme.googleanalytics_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ this.theme.googleanalytics_id }}');
</script>
{% endif %}

View File

@@ -0,0 +1,4 @@
<link rel="stylesheet" href="{{ ['assets/less/theme.less'] | theme }}">
{% styles %}
{% placeholder head %}

View File

@@ -0,0 +1,17 @@
<!-- Scripts -->
<script src="{{ [
'@jquery',
'@framework',
'@framework.extras',
'assets/js/app.js',
] | theme }}"></script>
{% scripts %}
{% flash %}
<p data-control="flash-message" data-interval="7" class="flashmessage {{ type }}">
{{ message }}
</p>
{% endflash %}
</body>
</html>

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% placeholder page_title default %}{{ this.page.title }}{% endplaceholder %}</title>
{% partial "meta/styles" %}
{% partial "meta/seo" %}
<meta name="generator" content="Winter CMS">
</head>
{% set pageId = this.page.id %}
{% set pageTitle = this.page.title %}
{% if pageId is empty %}
{% set pageId = page.id %}
{% endif %}
{% if pageTitle is empty %}
{% set pageTitle = page.title %}
{% endif %}
<body class="page-{{ pageId }} layout-{{ this.layout.id }}">

View File

@@ -0,0 +1,9 @@
name: "{{code}}"
description: "No description provided yet..."
author: "Winter CMS Scaffold"
homepage: "https://example.com"
code: "{{code}}"
form:
fields:
googleanalytics_id:
label: 'Google Analytics ID'

View File

@@ -0,0 +1 @@
1.0.1: 'Initial version'

View File

@@ -0,0 +1,2 @@
node_modules/
dist/

View File

@@ -0,0 +1,9 @@
# {{code}} Winter CMS Theme
This theme uses [Vite](https://wintercms.com/docs/develop/docs/console/asset-compilation-vite) for asset compilation. It also uses [Tailwind CSS](https://tailwindcss.com/).
## Getting Started
1. Run [`artisan vite:install`](https://wintercms.com/docs/console/asset-compilation#mix-install) and agree when asked to modify the `package.json` file for your project in order to register & install this theme's dependencies.
2. Run [`artisan vite:compile -p theme-{{code}} --production`](https://wintercms.com/docs/develop/docs/console/asset-compilation-vite#compile-a-vite-packages) to compile the asset files for this theme.
3. Optionally, run [`artisan vite:watch theme-{{code}}`](https://wintercms.com/docs/develop/docs/console/asset-compilation-vite#watch-a-vite-package) while actively working on the theme to have the assets automatically recompiled in the background for you every time you make a change.

View File

@@ -0,0 +1,9 @@
<?php
return [
'options' => [
'googleanalytics_id' => 'Google Analytics ID',
'color_primary' => 'Primary Color',
'color_secondary' => 'secondary Color',
],
];

View File

@@ -0,0 +1,8 @@
description = "Default layout"
default = true
==
{% partial "site/header" %}
{% page %}
{% partial "site/footer" %}

View File

@@ -0,0 +1,8 @@
title = "Page not found (404)"
url = "/404"
layout = "default"
==
<div>
<h1>Page not found</h1>
<p>We're sorry, but the page you requested cannot be found.</p>
</div>

View File

@@ -0,0 +1,8 @@
title = "Error page (500)"
url = "/error"
layout = "default"
==
<div>
<h1>Error</h1>
<p>We're sorry, but something went wrong and the page cannot be displayed.</p>
</div>

View File

@@ -0,0 +1,7 @@
title = "Home"
url = "/"
layout = "default"
==
<div class="container mx-auto">
<h1><span class="text-primary">Home</span> <span class="text-secondary">Page</span></h1>
</div>

View File

@@ -0,0 +1,11 @@
{% if this.theme.googleanalytics_id is not empty %}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ this.theme.googleanalytics_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ this.theme.googleanalytics_id }}');
</script>
{% endif %}

View File

@@ -0,0 +1,12 @@
==
{{ vite(['assets/src/css/theme-{{code}}.css'], 'theme-{{code}}') }}
<style>
:root {
--primary: {{ this.theme.color_primary }};
--secondary: {{ this.theme.color_secondary }};
}
</style>
{% styles %}
{% placeholder head %}

View File

@@ -0,0 +1,21 @@
<!-- Scripts -->
{# jQuery AJAX Framework #}
<script src="{{ [
'@jquery',
'@framework',
'@framework.extras',
] | theme }}"></script>
{# Vite extracted assets #}
{{ vite(['assets/src/js/theme-{{code}}.js'], 'theme-{{code}}') }}
{% scripts %}
{% flash %}
<p data-control="flash-message" data-interval="7" class="flashmessage {{ type }}">
{{ message }}
</p>
{% endflash %}
</body>
</html>

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% placeholder page_title default %}{{ this.page.title }}{% endplaceholder %}</title>
{% partial "meta/styles" %}
{% partial "meta/seo" %}
<meta name="generator" content="Winter CMS">
</head>
{% set pageId = this.page.id %}
{% set pageTitle = this.page.title %}
{% if pageId is empty %}
{% set pageId = page.id %}
{% endif %}
{% if pageTitle is empty %}
{% set pageTitle = page.title %}
{% endif %}
<body class="page-{{ pageId }} layout-{{ this.layout.id }}">

View File

@@ -0,0 +1,21 @@
name: "{{code}}"
description: "No description provided yet..."
author: "Winter CMS Scaffold"
homepage: "https://example.com"
code: "{{code}}"
form:
fields:
googleanalytics_id:
label: themes.{{code}}::lang.options.googleanalytics_id
type: text
span: full
color_primary:
label: themes.{{code}}::lang.options.color_primary
type: colorpicker
span: left
default: "#103141"
color_secondary:
label: themes.{{code}}::lang.options.color_secondary
type: colorpicker
span: right
default: "#2DA7C7"

View File

@@ -0,0 +1 @@
1.0.0: 'Initial version'