Skip to content

Commit

Permalink
fix bg image publication
Browse files Browse the repository at this point in the history
  • Loading branch information
lexoyo committed Sep 14, 2023
1 parent 4959283 commit 2edbc29
Show file tree
Hide file tree
Showing 17 changed files with 92 additions and 40 deletions.
1 change: 1 addition & 0 deletions .env.default
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ SILEX_FORCE_HTTPS_TRUST_XFP_HEADER=
SILEX_CORS_URL=
SILEX_CLIENT_CONFIG=
SILEX_FS_ROOT=
SILEX_FS_HOSTING_ROOT=
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ENV SILEX_FORCE_HTTPS_TRUST_XFP_HEADER=
ENV SILEX_CORS_URL=
ENV SILEX_CLIENT_CONFIG=
ENV SILEX_FS_ROOT=
ENV SILEX_FS_HOSTING_ROOT=
ENV SILEX_URL=

COPY . /silex
Expand Down
9 changes: 5 additions & 4 deletions examples/client-config-transformers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,20 @@
export default async function (config) {
config.addPublicationTransformers({
// Override how components render at publication by grapesjs
renderComponent(component, initialHtml) {
return initialHtml
renderComponent(component, toHtml) {
return toHtml()
},
// Override how styles render at publication by grapesjs
renderCssRule(rule, initialCss) {
return initialCss
return initialCss()
},
// Define how files are named
transformPath(path) {
return path
},
// Difine files URLs
transformPermalink(link) {
transformPermalink(link, type, initiator) {
console.log('transformPermalink', { link, type, initiator})
return link
},
// Transform files after they are rendered and before they are published
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"src/",
"public",
".silex.js",
".silex",
".env.default",
"package.json",
"package-lock.json",
Expand Down
Binary file modified public/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/assets/logo-silex-small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions src/ts/client/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import { getEditor, getEditorConfig } from './grapesjs'
import { CLIENT_CONFIG_FILE_NAME, DEFAULT_LANGUAGE, DEFAULT_WEBSITE_ID } from '../constants'
import { ConnectorId, WebsiteId } from '../types'
import { Editor, EditorConfig, Page } from 'grapesjs'
import { PublicationTransformer, validatePublicationTransformer } from './publication-transformers'
import { PublicationTransformer, publicationTransformerDefault, validatePublicationTransformer } from './publication-transformers'
import * as api from './api'
import { assetsPublicationTransformer } from './assetUrl'

/**
* @fileoverview Silex client side config
Expand Down Expand Up @@ -85,7 +86,14 @@ export class ClientConfig extends Config {
/**
* Publication transformers let plugins change files before they are published
*/
publicationTransformers: Array<PublicationTransformer> = []
publicationTransformers: Array<PublicationTransformer> = [assetsPublicationTransformer, publicationTransformerDefault]

/**
* Reset publication transformers
*/
resetPublicationTransformers() {
this.publicationTransformers = [assetsPublicationTransformer]
}

/**
* Add a publication transformer(s)
Expand Down
4 changes: 2 additions & 2 deletions src/ts/client/grapesjs/PublicationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { getPageSlug } from '../../page'
import { ApiConnectorLoggedInPostMessage, ApiConnectorLoginQuery, ApiPublicationPublishBody, ClientSideFile, ClientSideFileType, ConnectorData, ConnectorType, ConnectorUser, JobStatus, PublicationData, PublicationJobData, PublicationSettings, WebsiteData, WebsiteFile, WebsiteId, WebsiteSettings } from '../../types'
import { ApiConnectorLoggedInPostMessage, ApiConnectorLoginQuery, ApiPublicationPublishBody, ClientSideFile, ClientSideFileType, ConnectorData, ConnectorType, ConnectorUser, JobStatus, Initiator, PublicationData, PublicationJobData, PublicationSettings, WebsiteData, WebsiteFile, WebsiteId, WebsiteSettings } from '../../types'
import { Editor } from 'grapesjs'
import { PublicationUi } from './PublicationUi'
import { getUser, logout, publicationStatus, publish } from '../api'
Expand Down Expand Up @@ -323,7 +323,7 @@ export class PublicationManager {
const slug = getPageSlug(page.get('name'))
const cssInitialPath = `/css/${slug}.css`
const htmlInitialPath = `/${slug}.html`
const cssPermalink = transformPermalink(this.editor, cssInitialPath, ClientSideFileType.CSS)
const cssPermalink = transformPermalink(this.editor, cssInitialPath, ClientSideFileType.CSS, Initiator.HTML)
const cssPath = transformPath(this.editor, cssInitialPath, ClientSideFileType.CSS)
const htmlPath = transformPath(this.editor, htmlInitialPath, ClientSideFileType.HTML)
const body = this.editor.getHtml({ component })
Expand Down
1 change: 0 additions & 1 deletion src/ts/client/grapesjs/internal-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export const internalLinksPlugin = (editor, opts) => {
// mark all links as issues
const issues = []
onAll(component => {
console.log(component.getAttributes().href, getPageLink(page.previous('name')), page.previous('name'))
if(component.getAttributes().href === getPageLink(page.previous('name'))) {
issues.push(component)
}
Expand Down
4 changes: 0 additions & 4 deletions src/ts/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
*
*/

import { assetsPublicationTransformer } from './assetUrl'
import { ClientConfig } from './config'
import { ClientEvent } from './events'
import { initEditor, getEditor } from './grapesjs/index'
Expand All @@ -36,9 +35,6 @@ export async function start(options = {}) {
const config = new ClientConfig()
Object.assign(config, options)

// Add the publication transformers which are used to cleanup assets URLs
config.addPublicationTransformers(assetsPublicationTransformer)

// Config file in root folder which may be generated by the server
await config.addPlugin(config.clientConfigUrl, {})

Expand Down
58 changes: 45 additions & 13 deletions src/ts/client/publication-transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { Component, CssRule, Editor, ObjectStrings, Page } from 'grapesjs'
import { ClientSideFile, ClientSideFileType, ClientSideFileWithPermalink, PublicationData } from '../types'
import { ClientSideFile, ClientSideFileType, ClientSideFileWithPermalink, Initiator, PublicationData } from '../types'
import { onAll } from './utils'

/**
Expand Down Expand Up @@ -47,11 +47,42 @@ export interface PublicationTransformer {
// Transform files after they are rendered and before they are published
transformFile?(file: ClientSideFile): ClientSideFile
// Define files URLs
transformPermalink?(link: string, type: ClientSideFileType): string
transformPermalink?(link: string, type: ClientSideFileType, initiator: Initiator): string
// Define where files are published
transformPath?(path: string, type: ClientSideFileType): string
}

export const publicationTransformerDefault: PublicationTransformer = {
// Override how components render at publication by grapesjs
renderComponent(component: Component, toHtml: () => string): string | undefined {
return toHtml()
},
// Override how styles render at publication by grapesjs
renderCssRule(rule: CssRule, initialRule: () => ObjectStrings): ObjectStrings | undefined {
return initialRule()
},
// Define where files are published
transformPath(path: string, type: ClientSideFileType): string {
return path
},
// Define files URLs
transformPermalink(link: string, type: ClientSideFileType, initiator: Initiator): string {
switch(initiator) {
case Initiator.HTML:
return link
case Initiator.CSS:
// In case of a link from a CSS file, we need to go up one level
return `../${link.replace(/^\//, '')}`
default:
throw new Error(`Unknown initiator ${initiator}`)
}
},
// Define how files are named
transformFile(file: ClientSideFile): ClientSideFile {
return file
}
}

export function validatePublicationTransformer(transformer: PublicationTransformer): void {
// List all the properties
const allowedProperties = [
Expand Down Expand Up @@ -97,32 +128,32 @@ export function renderComponents(editor: Editor) {
c[ATTRIBUTE_METHOD_STORE_HREF] = href
c.set('attributes', {
...c.get('attributes'),
href: transformPermalink(editor, href, ClientSideFileType.HTML),
href: transformPermalink(editor, href, ClientSideFileType.HTML, Initiator.HTML),
})
}
// Handle both c.attributes.src and c.attributes.attributes.src
// For some reason we need both
// Especially when the component is not on the current page, we need c.attributes.attributes.src
if(c.get('attributes').src) {
c[ATTRIBUTE_METHOD_STORE_SRC] = c.get('attributes').src
const src = transformPermalink(editor, c.get('attributes').src, ClientSideFileType.ASSET)
const src = transformPermalink(editor, c.get('attributes').src, ClientSideFileType.ASSET, Initiator.HTML)
c.set('attributes', {
...c.get('attributes'),
src,
})
}
if(c.get('src')) {
c[ATTRIBUTE_METHOD_STORE_ATTRIBUTES_SRC] = c.get('src')
const src = transformPermalink(editor, c.get('src'), ClientSideFileType.ASSET)
const src = transformPermalink(editor, c.get('src'), ClientSideFileType.ASSET, Initiator.HTML)
c.set('src', src)
}
c.toHTML = () => {
return config.publicationTransformers.reduce((html: string, transformer: PublicationTransformer) => {
return config.publicationTransformers.reduce((xxx: string, transformer: PublicationTransformer) => {
try {
return transformer.renderComponent ? transformer.renderComponent(c, () => html) ?? html : html
return transformer.renderComponent ? transformer.renderComponent(c, () => xxx) ?? xxx : xxx
} catch (e) {
console.error('Publication transformer: error rendering component', c, e)
return html
return xxx
}
}, initialToHTML())
}
Expand All @@ -145,11 +176,12 @@ export function renderCssRules(editor: Editor) {
style[ATTRIBUTE_METHOD_STORE_CSS] = style.getStyle
style.getStyle = () => {
try {
const initialStyle = transformBgImage(editor, initialGetStyle())
const result = config.publicationTransformers.reduce((s: CssRule, transformer: PublicationTransformer) => {
return {
...transformer.renderCssRule ? transformer.renderCssRule(s, initialGetStyle) ?? s : s,
...transformer.renderCssRule ? transformer.renderCssRule(s, () => initialStyle) ?? s : s,
}
}, transformBgImage(editor, initialGetStyle()))
}, initialStyle)
return result
} catch (e) {
console.error('Publication transformer: error rendering style', style, e)
Expand All @@ -169,7 +201,7 @@ export function transformBgImage(editor: Editor, style: ObjectStrings): ObjectSt
if (bgUrl) {
return {
...style,
'background-image': `url(${transformPermalink(editor, bgUrl, ClientSideFileType.ASSET)})`,
'background-image': `url(${transformPermalink(editor, bgUrl, ClientSideFileType.ASSET, Initiator.CSS)})`,
}
}
return style
Expand Down Expand Up @@ -198,11 +230,11 @@ export function transformFiles(editor: Editor, data: PublicationData) {
* Transform files paths
* Exported for unit tests
*/
export function transformPermalink(editor: Editor, path: string, type: ClientSideFileType): string {
export function transformPermalink(editor: Editor, path: string, type: ClientSideFileType, initiator: Initiator): string {
const config = editor.getModel().get('config')
return config.publicationTransformers.reduce((result: string, transformer: PublicationTransformer) => {
try {
return transformer.transformPermalink ? transformer.transformPermalink(result, type) ?? result : result
return transformer.transformPermalink ? transformer.transformPermalink(result, type, initiator) ?? result : result
} catch (e) {
console.error('Publication transformer: error transforming path', result, e)
return result
Expand Down
4 changes: 3 additions & 1 deletion src/ts/server/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const options = parse({
'protocol': ['', 'Sets the protocol param of the express module "cookie-session".', 'string'],
'config': [ 'c', 'Path for the server side config file to load at startup', 'file'],
'client-config': ['', 'Path to client config file to be served on .silex-client.js', 'string'],
'fs-root': ['', 'Path to the root folder where to store websites. Used by the default connector (fs).', 'string'],
'fs-root': ['', 'Path to the root folder where to store websites. Used by the default storage connector (fs).', 'string'],
'fs-hosting-root': ['', 'Path to the root folder where to publish websites. Used by the default hosting connector (fs).', 'string'],
'session-name': ['', 'Sets the name param of the express module "cookie-session".', 'string'],
'session-secret': ['s', 'Session secret', 'string'],
'ssl-port': [ false, 'Port to listen to for SSL/HTTPS', 'int'],
Expand All @@ -49,6 +50,7 @@ if(options['protocol']) process.env.SILEX_PROTOCOL = options['protocol']
if(options.config) process.env.SILEX_CONFIG = options.config
if(options['client-config']) process.env.SILEX_CLIENT_CONFIG = options['client-config']
if(options['fs-root']) process.env.SILEX_FS_ROOT = options['fs-root']
if(options['fs-hosting-root']) process.env.SILEX_FS_HOSTING_ROOT = options['fs-hosting-root']
if(options['session-name']) process.env.SILEX_SESSION_NAME = options['session-name']
if(options['session-secret']) process.env.SILEX_SESSION_SECRET = options['session-secret']
if(options['ssl-port']) process.env.SILEX_SSL_PORT = options['ssl-port']
Expand Down
5 changes: 3 additions & 2 deletions src/ts/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { FsStorage } from './connectors/FsStorage'
import { Connector } from './connectors/connectors'
import { ConnectorType } from '../types'
import { FsHosting } from './connectors/FsHosting'
import { tmpdir } from 'os'

/**
* Config types definitions
Expand Down Expand Up @@ -176,13 +177,13 @@ export class ServerConfig extends Config {
// Add default storage connectors
if (!this.storageConnectors.length) {
this.addStorageConnector(new FsStorage(null, {
path: process.env.SILEX_FS_ROOT,
path: process.env.SILEX_FS_ROOT || tmpdir() + '/silex',
}))
}
// Add default hosting connectors
if (!this.hostingConnectors.length) {
this.addHostingConnector(new FsHosting(null, {
path: process.env.SILEX_FS_ROOT,
path: process.env.SILEX_FS_HOSTING_ROOT || tmpdir() + '/silex/publication',
}))
}
}
Expand Down
15 changes: 10 additions & 5 deletions src/ts/server/connectors/FsHosting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import fs from 'fs/promises'
import { ConnectorFile, StorageConnector, HostingConnector, StatusCallback, ConnectorSession, contentToString, toConnectorData, ConnectorFileContent} from './connectors'
import { join } from 'path'
import { FsStorage } from './FsStorage'
Expand All @@ -23,21 +24,25 @@ import { JobManager } from '../jobs'

type FsSession = ConnectorSession

interface FsOptions {
path?: string
}

export class FsHosting extends FsStorage implements HostingConnector<FsSession> {
connectorId = 'fs-hosting'
displayName = 'File system hosting'
connectorType = ConnectorType.HOSTING

protected async initFs() {
const stat = await fs.stat(this.options.path).catch(() => null)
if (!stat) {
await fs.mkdir(join(this.options.path, 'assets'), { recursive: true })
await fs.mkdir(join(this.options.path, 'css'), { recursive: true })
}
}

async publish(session: FsSession, id: WebsiteId, files: ConnectorFile[], {startJob, jobSuccess, jobError}: JobManager): Promise<JobData> {
const job = startJob(`Publishing to ${this.displayName}`) as PublicationJobData
job.logs = [[`Publishing to ${this.displayName}`]]
job.errors = [[]]
// Call write without id or folder so that it goes in / (path will be modified by publication transformers)
this.write(session, '', files, '', async ({status, message}) => {
await this.write(session, '', files, '', async ({status, message}) => {
// Update the job status
job.status = status
job.message = message
Expand Down
6 changes: 3 additions & 3 deletions src/ts/server/connectors/FsStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import fs, { stat } from 'fs/promises'
import fs from 'fs/promises'
import { createWriteStream } from 'fs'
import { ConnectorFile, StorageConnector, StatusCallback, ConnectorSession, toConnectorData, ConnectorFileContent} from './connectors'
import { dirname, join } from 'path'
Expand Down Expand Up @@ -64,7 +64,7 @@ export class FsStorage implements StorageConnector<FsSession> {
this.initFs()
}

private async initFs() {
protected async initFs() {
const stat = await fs.stat(this.options.path).catch(() => null)
if (!stat) {
// create data folder with a default website
Expand Down Expand Up @@ -136,7 +136,7 @@ export class FsStorage implements StorageConnector<FsSession> {
async getWebsiteMeta(session: FsSession, id: WebsiteId): Promise<WebsiteMeta> {
const websiteId = requiredParam<WebsiteId>(id, 'website id')
// Get stats for website folder
const fileStat = await stat(join(this.options.path, websiteId))
const fileStat = await fs.stat(join(this.options.path, websiteId))
const path = join(this.options.path, websiteId, WEBSITE_META_DATA_FILE)
// Get meta file
const content = await fs.readFile(path)
Expand Down
6 changes: 4 additions & 2 deletions src/ts/server/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const TIME_TO_KEEP_AFTER_STOP = 60*10*1000 // 10 min

// Store the jobs in memory
const jobs = new Array<JobData>()
const timers = new WeakMap<JobData, NodeJS.Timeout>()

// Key which will change on server restart
const processKey = Math.ceil(Math.random() * 1000000)
Expand Down Expand Up @@ -60,7 +61,8 @@ export function jobError(id: JobId, message = ''): boolean {
// Remove a job from memory
// This is also used in unit tests to avoid memory lea
export function killJob(job: JobData) {
clearTimeout(job._timeout)
clearTimeout(timers.get(job))
timers.delete(job)
jobs.splice(jobs.findIndex(j => job.jobId === j.jobId), 1)
}

Expand All @@ -70,7 +72,7 @@ function end(id: JobId, status: JobStatus, message = ''): boolean {
if(job) {
job.status = status
if(message) job.message = message
job._timeout = setTimeout(() => killJob(job), TIME_TO_KEEP_AFTER_STOP)
timers.set(job, setTimeout(() => killJob(job), TIME_TO_KEEP_AFTER_STOP))
return true
}
return false
Expand Down
5 changes: 5 additions & 0 deletions src/ts/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,11 @@ export enum ClientSideFileType {
CSS = 'css',
}

export enum Initiator {
HTML = 'html',
CSS = 'css',
}

/**
* Type for a client side file when the content is not available, used to handle file names and paths and urls
*/
Expand Down

0 comments on commit 2edbc29

Please sign in to comment.