Skip to content

Commit

Permalink
add sections to settings with silex client side API
Browse files Browse the repository at this point in the history
  • Loading branch information
lexoyo committed Nov 20, 2023
1 parent 9f42827 commit 18c6e8a
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 99 deletions.
4 changes: 2 additions & 2 deletions src/html/index.pug
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ html(lang="en", translate="no")
script(src='js/main.js')

block silex-start
// Here you can use silex.config to hook into silex options before silex starts
script.
window.silex.start()
silex.start()
// From here silex.config is available and Silex is getting started
16 changes: 16 additions & 0 deletions src/ts/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { Editor, EditorConfig, Page } from 'grapesjs'
import { PublicationTransformer, publicationTransformerDefault, validatePublicationTransformer } from './publication-transformers'
import * as api from './api'
import { assetsPublicationTransformer } from './assetUrl'
import { SettingsSection } from './grapesjs/settings-sections'
import { addSection, removeSection } from './grapesjs/settings'

/**
* @fileoverview Silex client side config
Expand Down Expand Up @@ -128,4 +130,18 @@ export class ClientConfig extends Config {
// Add to the list
this.publicationTransformers = this.publicationTransformers.concat(transformers)
}

/**
* Add a section to the settings dialog
*/
addSettings(section: SettingsSection, siteOrPage: 'site' | 'page', position: 'first' | 'last' | number = 'last') {
addSection(section, siteOrPage, position)
}

/**
* Remove a section from the settings dialog
*/
removeSettings(id: string, siteOrPage: 'site' | 'page') {
removeSection(id, siteOrPage)
}
}
117 changes: 117 additions & 0 deletions src/ts/client/grapesjs/settings-sections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {live} from 'lit-html/directives/live.js'
import {html} from 'lit-html'
import { WebsiteSettings } from '../../types'

// ID of the code editor wrapper
export const idCodeWrapper = 'settings-head-wrapper'

// Is the model a site or a page?
export function isSite(model) { return !!model.getHtml }

export interface SettingsSection {
id: string
label: string
render: (settings: WebsiteSettings, model) => any
}

/**
* This is the settings dialog default sections
* Each section has an entry in the sidebar
* Each section render as part of the settings form
* The form is submitted to save the settings with all FormData
*/
export const defaultSections: SettingsSection[] = [{
id: 'general',
label: 'General',
render: (settings, model) => html`
<div id="settings-general" class="silex-hideable">
<div class="gjs-sm-sector-title">General</div>
<div class="silex-form__group col2">
<label class="silex-form__element">
<h3>${isSite(model) ? 'Site name' : 'Page name'}</h3>
<p class="silex-help">${isSite(model) ? 'The project name in the editor, for you to remember what it is about.' : 'Label of the page in the editor, and file name of the published HTML page.'}</p>
<input type="text" name="name" .value=${live(model.get('name') || '')}/>
</label>
<label class="silex-form__element">
<h3>Website language</h3>
<p class="silex-help">This is the default language code for this website. Example: en, fr, es...</p>
<input type="text" name="lang" .value=${live(settings.lang || '')}/>
</label>
</div>
</div>
`,
}, {
id: 'seo',
label: 'SEO',
render: (settings, model) => html`
<div id="settings-seo" class="silex-hideable silex-hidden">
<div class="gjs-sm-sector-title">SEO</div>
<div class="silex-form__group col2">
<label class="silex-form__element">
<h3>Title</h3>
<p class="silex-help">Title of the browser window, and title in the search engines results. It is used by search engines to find out what your site is talking about. The title should be a maximum of 70 characters long, including spaces.</p>
<input type="text" name="title" .value=${live(settings.title || '')}/>
</label>
<label class="silex-form__element">
<h3>Description</h3>
<p class="silex-help">Description displayed by the search engines in search results. It is used by search engines to find out what your site is talking about. It is best to keep meta descriptions between 150 and 160 characters.</p>
<input type="text" name="description" .value=${live(settings.description || '')}/>
</label>
<label class="silex-form__element">
<h3>Favicon</h3>
<p class="silex-help">Small image displayed in the browser's address bar and in tabs. The recommended size is 16×16 or 32×32 pixels. This can be a URL or a relative path.</p>
<input type="text" name="favicon" .value=${live(settings.favicon || '')}/>
</label>
</div>
</div>
`,
}, {
id: 'social',
label: 'Social',
render: (settings, model) => html`
<div id="settings-social" class="silex-hideable silex-hidden">
<div class="gjs-sm-sector-title">Social</div>
<div class="silex-help">
<p>Once your website is live, you can use these tools to test sharing:&nbsp;<a target="_blank" href="https://developers.facebook.com/tools/debug/">Facebook</a>,
<a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter</a>,
<a target="_blank" href="https://www.linkedin.com/post-inspector/inspect/">Linkedin</a></p>
</div>
<div class="silex-form__group col2">
<label class="silex-form__element">
<h3>Title</h3>
<p class="silex-help">The title of your website displayed when a user shares your website on a social network.
Do not include any branding in this title, just eye-catching phrase, e.g. "Learn everything about fishing".
Title should be between 60 and 90 characters long.</p>
<input type="text" name="og:title" .value=${live(settings['og:title'] || '')}/>
</label>
<label class="silex-form__element">
<h3>Description</h3>
<p class="silex-help">Description displayed when a user shares your website on a social network. Make it catchy, and invite readers to visit your website too, e.g. "Sam's website about fishing, check it out!" Title should be between 60 and 90 characters long.</p>
<input type="text" name="og:description" .value=${live(settings['og:description'] || '')}/>
</label>
<label class="silex-form__element">
<h3>Image</h3>
<div class="silex-help">
<p>Thumbnail image which is displayed when your website is shared on a social network. The optimal size is 1200×627 pixels. At this size, your thumbnail will be big and stand out from the crowd. But do not exceed the 5MB size limit. If you use an image that is smaller than 400 pixels x 209 pixels, it will render as a much smaller thumbnail.</p>
<p>Please enter the full URL here, e.g. "http://mysite.com/path/to/image.jpg"</p>
</div>
<input type="text" name="og:image" .value=${live(settings['og:image'] || '')}/>
</label>
</div>
</div>
`,
}, {
id: 'code',
label: 'Code',
render: (settings, model) => html`
<div id="settings-code" class="silex-hideable silex-hidden">
<div class="gjs-sm-sector-title">Code</div>
<div class="silex-form__group">
<label class="silex-form__element" id="${idCodeWrapper}">
<h3>HTML head</h3>
<p class="silex-help">HTML code which will be inserted in the HEAD tag.</p>
</label>
</div>
</div>
`,
}]
131 changes: 36 additions & 95 deletions src/ts/client/grapesjs/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,17 @@
*/

import {html, render} from 'lit-html'
import {live} from 'lit-html/directives/live.js'
import {defaultSections, idCodeWrapper, isSite} from './settings-sections'

/**
* @fileoverview This file contains the settings dialog. The config API lets plugins add sections to the settings dialog.
*/

import { WebsiteSettings } from '../../types'

const menuItems = [{
id: 'general',
label: 'General',
}, {
id: 'seo',
label: 'SEO',
}, {
id: 'social',
label: 'Social',
}, {
id: 'code',
label: 'Code',
}]
let currentMenuItem = menuItems[0]
const sectionsSite = [...defaultSections]
const sectionsPage = [...defaultSections]

const idCodeWrapper = 'settings-head-wrapper'
const el = document.createElement('div')
let modal

Expand Down Expand Up @@ -104,12 +95,35 @@ export const settingsDialog = (editor, opts) => {
})
}

// Is the model a site or a page?
function isSite(model) { return !!model.getHtml }
export function addSection(section, siteOrPage: 'site' | 'page', position: 'first' | 'last' | number) {
const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
if (position === 'first') {
sections.unshift(section)
} else if (position === 'last') {
sections.push(section)
} else if (typeof position === 'number') {
sections.splice(position, 0, section)
} else {
throw new Error('Invalid position for settings section')
}
}

export function removeSection(id, siteOrPage: 'site' | 'page') {
const sections = siteOrPage === 'site' ? sectionsSite : sectionsPage
const index = sections.findIndex(section => section.id === id)
if(index === -1) throw new Error(`Cannot find section with id ${id}`)
sections.splice(index, 1)
}


function displaySettings(editor, config, model = editor.getModel()) {
// Update the model with the current settings
const settings = model.get('settings') || {} as WebsiteSettings
model.set('settings', settings)
// Get the current sections for page or site
const menuItemsCurrent = isSite(model) ? sectionsSite : sectionsPage
// Init the current section selection
let sections = menuItemsCurrent[0]
render(html`
<form class="silex-form">
<div class="silex-help">
Expand All @@ -124,12 +138,12 @@ function displaySettings(editor, config, model = editor.getModel()) {
<section class="silex-sidebar-dialog">
<aside class="silex-bar">
<ul class="silex-list silex-list--menu">
${menuItems.map(item => html`
${menuItemsCurrent.map(item => html`
<li
class=${item.id === currentMenuItem.id ? 'active' : ''}
class=${item.id === sections.id ? 'active' : ''}
@click=${e => {
e.preventDefault()
currentMenuItem = item
sections = item
const li = e.target as HTMLElement
const ul = li.closest('ul')
const section = li.closest('section')
Expand All @@ -150,80 +164,7 @@ function displaySettings(editor, config, model = editor.getModel()) {
</ul>
</aside>
<main>
<div id="settings-general" class="silex-hideable">
<div class="gjs-sm-sector-title">General</div>
<div class="silex-form__group col2">
<label class="silex-form__element">
<h3>${isSite(model) ? 'Site name' : 'Page name'}</h3>
<p class="silex-help">${isSite(model) ? 'The project name in the editor, for you to remember what it is about.' : 'Label of the page in the editor, and file name of the published HTML page.'}</p>
<input type="text" name="name" .value=${live(model.get('name') || '')}/>
</label>
<label class="silex-form__element">
<h3>Website language</h3>
<p class="silex-help">This is the default language code for this website. Example: en, fr, es...</p>
<input type="text" name="lang" .value=${live(settings.lang || '')}/>
</label>
</div>
</div>
<div id="settings-seo" class="silex-hideable silex-hidden">
<div class="gjs-sm-sector-title">SEO</div>
<div class="silex-form__group col2">
<label class="silex-form__element">
<h3>Title</h3>
<p class="silex-help">Title of the browser window, and title in the search engines results. It is used by search engines to find out what your site is talking about. The title should be a maximum of 70 characters long, including spaces.</p>
<input type="text" name="title" .value=${live(settings.title || '')}/>
</label>
<label class="silex-form__element">
<h3>Description</h3>
<p class="silex-help">Description displayed by the search engines in search results. It is used by search engines to find out what your site is talking about. It is best to keep meta descriptions between 150 and 160 characters.</p>
<input type="text" name="description" .value=${live(settings.description || '')}/>
</label>
<label class="silex-form__element">
<h3>Favicon</h3>
<p class="silex-help">Small image displayed in the browser's address bar and in tabs. The recommended size is 16×16 or 32×32 pixels. This can be a URL or a relative path.</p>
<input type="text" name="favicon" .value=${live(settings.favicon || '')}/>
</label>
</div>
</div>
<div id="settings-social" class="silex-hideable silex-hidden">
<div class="gjs-sm-sector-title">Social</div>
<div class="silex-help">
<p>Once your website is live, you can use these tools to test sharing:&nbsp;<a target="_blank" href="https://developers.facebook.com/tools/debug/">Facebook</a>,
<a target="_blank" href="https://cards-dev.twitter.com/validator">Twitter</a>,
<a target="_blank" href="https://www.linkedin.com/post-inspector/inspect/">Linkedin</a></p>
</div>
<div class="silex-form__group col2">
<label class="silex-form__element">
<h3>Title</h3>
<p class="silex-help">The title of your website displayed when a user shares your website on a social network.
Do not include any branding in this title, just eye-catching phrase, e.g. "Learn everything about fishing".
Title should be between 60 and 90 characters long.</p>
<input type="text" name="og:title" .value=${live(settings['og:title'] || '')}/>
</label>
<label class="silex-form__element">
<h3>Description</h3>
<p class="silex-help">Description displayed when a user shares your website on a social network. Make it catchy, and invite readers to visit your website too, e.g. "Sam's website about fishing, check it out!" Title should be between 60 and 90 characters long.</p>
<input type="text" name="og:description" .value=${live(settings['og:description'] || '')}/>
</label>
<label class="silex-form__element">
<h3>Image</h3>
<div class="silex-help">
<p>Thumbnail image which is displayed when your website is shared on a social network. The optimal size is 1200×627 pixels. At this size, your thumbnail will be big and stand out from the crowd. But do not exceed the 5MB size limit. If you use an image that is smaller than 400 pixels x 209 pixels, it will render as a much smaller thumbnail.</p>
<p>Please enter the full URL here, e.g. "http://mysite.com/path/to/image.jpg"</p>
</div>
<input type="text" name="og:image" .value=${live(settings['og:image'] || '')}/>
</label>
</div>
</div>
<div id="settings-code" class="silex-hideable silex-hidden">
<div class="gjs-sm-sector-title">Code</div>
<div class="silex-form__group">
<label class="silex-form__element" id="${idCodeWrapper}">
<h3>HTML head</h3>
<p class="silex-help">HTML code which will be inserted in the HEAD tag.</p>
</label>
</div>
</div>
${menuItemsCurrent.map(item => item.render(settings, model))}
</main>
</section>
<footer>
Expand Down
9 changes: 7 additions & 2 deletions src/ts/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ import { initEditor, getEditor } from './grapesjs/index'
// Expose API to calling app as window.silex
export * from './expose'

/**
* Expose the config object
*/
export let config: ClientConfig

/**
* Start Silex, called from host HTML page with window.silex.start()
*/
export async function start(options = {}) {
const config = new ClientConfig()
export async function start(options = {}): Promise<void> {
config = new ClientConfig()
Object.assign(config, options)

// Config file in root folder which may be generated by the server
Expand Down

0 comments on commit 18c6e8a

Please sign in to comment.