@newlogic-digital/core 1.1.1 → 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,58 +19,42 @@ Starter for creating web applications. Powered by Vite and Vituum.
19
19
  - 📦 Modular structure
20
20
  - ✉️ Email templates
21
21
 
22
- Newlogic Core is an integration for [Vituum](https://vituum.dev), and contains set of tools that can be used to create modern web applications.
22
+ Newlogic Core is a plugin for [Vite](https://vitejs.dev), and contains set of plugins that can be used to create modern web applications.
23
23
 
24
- We use it as our main front-end tool at [Newlogic Digital](https://www.newlogic.cz/) to create wonders.
24
+ We use it as our main front-end set of tools at [Newlogic Digital](https://www.newlogic.cz/) to create wonders.
25
25
 
26
26
  ## 🛠️ Integrated tools
27
27
  * **[Vite](https://vitejs.dev)** next-generation frontend tooling
28
28
  * **[Vituum](https://vituum.dev)** fast prototyping with template engines
29
- * **[PostCSS](https://postcss.org/)** with basic plugins and [Tailwind CSS](https://tailwindcss.com/) for utility classes.
30
- * **[TwigJS](https://github.com/vituum/vite-plugin-twig)** as template engine twig
29
+ * **[PostCSS](https://postcss.org/)** with basic plugins
30
+ * **[TailwindCSS](https://tailwindcss.com/)** for utility classes
31
31
  * **[Latte](https://github.com/vituum/vite-plugin-latte)** as template engine latte
32
32
 
33
- ### 💡 Basic principle
34
-
35
- Most of today build tools are hard to configure and not focused primary on PHP server side applications.
36
-
37
- PHP programmers often **don't want to configure anything**, basic idea is to add as many files you want to `src` and get output in `public/assets` - without worrying about anything.
38
-
39
- It doesn't matter if you use Nette, Symfony or Laravel - the structure can be freely adjusted as needed - `resources` and` public`, `src` and` dist` or `app/assets` and` www`
40
-
41
- It's up to you - all paths are freely configurable via `vite.config.js` config
42
-
43
- ### 📦 Modularity
44
-
45
- Newlogic Core uses [Vituum](https://vituum.dev) and [Vite](https://vitejs.dev) for frontend tooling.
46
-
47
- Source files are divided by modules inside `src` directory - styles, scripts, templates, data, emails, assets. It is optional which modules you want to use for the project, simple delete the directory. You really only use what you want to use.
48
-
49
33
  ## 🪄 Get started
50
34
 
51
35
  ```sh
52
36
  npm i @newlogic-digital/core --save-dev
53
37
  ```
54
38
 
55
- ### Requirements
56
-
57
- - [Node.js LTS (16.x)](https://nodejs.org/en/download/)
58
- - [Vituum](https://vituum.dev/)
59
-
60
39
  ### Config
61
40
 
62
41
  Each **Newlogic Core** project needs to have config via `vite.config.js`
63
42
 
64
43
  ```js
65
- import { defineConfig } from 'vituum'
66
44
  import core from "@newlogic-digital/core"
67
45
 
68
- export default defineConfig({
69
- integrations: [core()]
70
- })
46
+ export default {
47
+ plugins: [core()]
48
+ }
71
49
  ```
72
50
 
73
51
  You can also try minimal example project [core-starter](https://github.com/newlogic-digital/core-starter)
74
52
 
53
+ ### Requirements
54
+
55
+ - [Node.js LTS (18.x)](https://nodejs.org/en/download/)
56
+ - [Vite](https://vitejs.dev/)
57
+ - [PHP 8.2](https://www.php.net/) for Latte support
58
+
75
59
  ## Licence
76
60
  MIT
package/index.js CHANGED
@@ -1,37 +1,36 @@
1
- import posthtml from '@vituum/posthtml'
2
- import juice from '@vituum/juice'
3
- import twig from '@vituum/twig'
4
- import latte from '@vituum/latte'
5
- import lodash from 'lodash'
6
- import minifier from 'html-minifier-terser'
7
- import fs from 'fs'
8
- import fse from 'fs-extra'
9
- import { dirname, resolve } from 'path'
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import { dirname, resolve, join, relative } from 'node:path'
10
4
  import postHtml from 'posthtml'
11
- import highlight from './prism.js'
12
- import tailwindcss from 'tailwindcss'
13
- import tailwindcssNesting from 'tailwindcss/nesting/index.js'
14
- import postcssImport from 'postcss-import'
15
- import postcssNesting from 'postcss-nesting'
16
- import postcssCustomMedia from 'postcss-custom-media'
17
- import postcssCustomSelectors from 'postcss-custom-selectors'
18
- import autoprefixer from 'autoprefixer'
19
- import chalk from 'chalk'
5
+ import vituum from 'vituum'
6
+ import posthtml from '@vituum/vite-plugin-posthtml'
7
+ import latte from '@vituum/vite-plugin-latte'
8
+ import twig from '@vituum/vite-plugin-twig'
9
+ import juice from '@vituum/vite-plugin-juice'
10
+ import send from '@vituum/vite-plugin-send'
11
+ import tailwindcss from '@vituum/vite-plugin-tailwindcss'
12
+ import { getPackageInfo, merge } from 'vituum/utils/common.js'
13
+ import highlight from './src/prism.js'
14
+ import twigOptions from './src/twig.js'
20
15
  import FastGlob from 'fast-glob'
16
+ import fse from 'fs-extra'
17
+ import pc from 'picocolors'
18
+
19
+ const { name } = getPackageInfo(import.meta.url)
21
20
 
22
21
  const posthtmlPrism = {
23
- name: '@vituum/vite-plugin-posthtml-prism',
22
+ name: '@newlogic-digital/vite-plugin-posthtml-prism',
24
23
  enforce: 'post',
25
24
  transformIndexHtml: {
26
25
  enforce: 'post',
27
- transform: async(html, { filename }) => {
26
+ transform: async (html, { filename }) => {
28
27
  filename = filename.replace('?raw', '')
29
28
 
30
- if (!filename.endsWith('ui.json') && !filename.endsWith('ui.vituum.json.html')) {
29
+ if (!filename.replace('.html', '').endsWith('ui.json')) {
31
30
  return
32
31
  }
33
32
 
34
- const plugins = [highlight({ inline: false })]
33
+ const plugins = [highlight({ inline: false })]
35
34
 
36
35
  const result = await postHtml(plugins).process(html)
37
36
 
@@ -40,206 +39,36 @@ const posthtmlPrism = {
40
39
  }
41
40
  }
42
41
 
43
- const wrapPreCode = (code, lang) => {
44
- return `<pre class="language-${lang}"><code class="language-${lang}">${code}</code></pre>`
45
- }
46
-
47
- const stripIndent = (string) => {
48
- const indent = () => {
49
- const match = string.match(/^[ \t]*(?=\S)/gm)
50
-
51
- if (!match) {
52
- return 0
53
- }
54
-
55
- return match.reduce((r, a) => Math.min(r, a.length), Infinity)
56
- }
57
-
58
- if (indent() === 0) {
59
- return string
60
- }
61
-
62
- const regex = new RegExp(`^[ \\t]{${indent()}}`, 'gm')
63
-
64
- return string.replace(regex, '')
65
- }
66
-
67
- const parseMinifyHtml = async (input, name) => {
68
- const minify = await minifier.minify(input, {
69
- collapseWhitespace: true,
70
- collapseInlineTagWhitespace: false,
71
- minifyCSS: true,
72
- removeAttributeQuotes: true,
73
- quoteCharacter: '\'',
74
- minifyJS: true
75
- })
76
-
77
- if (name) {
78
- return JSON.stringify({
79
- [name]: minify
80
- })
81
- } else {
82
- return JSON.stringify(minify)
83
- }
84
- }
85
-
86
- const defaultConfig = {
87
- format: 'twig',
42
+ /**
43
+ * @type {import('@newlogic-digital/core/types').PluginUserConfig}
44
+ */
45
+ const defaultOptions = {
46
+ mode: null,
47
+ cert: 'localhost',
48
+ format: ['latte'],
88
49
  emails: {
89
50
  outputDir: resolve(process.cwd(), 'public/email'),
90
51
  appDir: resolve(process.cwd(), 'app/Templates/Emails')
91
52
  },
92
- posthtml: {},
93
- juice: {},
94
- tailwind: {},
95
- twig: {
96
- namespaces: {
97
- src: resolve(process.cwd(), 'src'),
98
- templates: resolve(process.cwd(), 'src/templates')
99
- },
100
- functions: {
101
- pages: () => {
102
- return fs.readdirSync('src/views').filter(file => fs.statSync('src/views/' + file).isFile())
103
- },
104
- fetch: (data) => {
105
- if (typeof data !== 'undefined') {
106
- if (data.indexOf('http') > -1) {
107
- return data
108
- } else {
109
- let slash = data.indexOf('/') + 1
110
- if (slash > 1) {
111
- slash = 0
112
- }
113
-
114
- return fs.readFileSync(process.cwd() + '/' + data.substring(slash, data.length), 'utf8').toString()
115
- }
116
- }
117
- },
118
- randomColor: () => {
119
- return '#' + Math.random().toString(16).slice(2, 8)
120
- },
121
- placeholder: (width, height) => {
122
- const colors = ['333', '444', '666', '222', '777', '888', '111']
123
- return 'https://via.placeholder.com/' + width + 'x' + height + '/' + colors[Math.floor(Math.random() * colors.length)] + '.webp'
124
- },
125
- lazy: (width, height) => {
126
- const svg = encodeURIComponent(stripIndent('<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + width + ' ' + height + '"></svg>'))
127
-
128
- return 'data:image/svg+xml;charset=UTF-8,' + svg
129
- },
130
- ratio: (width, height) => {
131
- return (height / width) * 100
132
- }
133
- },
134
- filters: {
135
- asset: (url) => {
136
- return url.replace('/src/', '/')
137
- },
138
- rem: (value) => {
139
- return `${value / 16}rem`
140
- },
141
- encode64: (path) => {
142
- const svg = encodeURIComponent(stripIndent(path))
143
-
144
- return 'data:image/svg+xml;charset=UTF-8,' + svg
145
- },
146
- exists: (path) => {
147
- if (path.indexOf('/') === 0) {
148
- path = path.slice(1)
149
- }
150
-
151
- return fs.existsSync(resolve(process.cwd(), path))
152
- },
153
- tel: (value) => {
154
- return value.replace(/\s+/g, '').replace('(', '').replace(')', '')
155
- }
156
- },
157
- extensions: [
158
- (Twig) => {
159
- Twig.exports.extendTag({
160
- type: 'json',
161
- regex: /^json\s+(.+)$|^json$/,
162
- next: ['endjson'],
163
- open: true,
164
- compile: function(token) {
165
- const expression = token.match[1] ?? '\'_null\''
166
-
167
- token.stack = Reflect.apply(Twig.expression.compile, this, [{
168
- type: Twig.expression.type.expression,
169
- value: expression
170
- }]).stack
171
-
172
- delete token.match
173
- return token
174
- },
175
- parse: async function(token, context, chain) {
176
- const name = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
177
- const output = this.parse(token.output, context)
178
-
179
- if (name === '_null') {
180
- return {
181
- chain,
182
- output: await parseMinifyHtml(output)
183
- }
184
- } else {
185
- return {
186
- chain,
187
- output: await parseMinifyHtml(output, name)
188
- }
189
- }
190
- }
191
- })
192
- Twig.exports.extendTag({
193
- type: 'endjson',
194
- regex: /^endjson$/,
195
- next: [],
196
- open: false
197
- })
198
- },
199
- (Twig) => {
200
- Twig.exports.extendTag({
201
- type: "code",
202
- regex: /^code\s+(.+)$/,
203
- next: ["endcode"], // match the type of the end tag
204
- open: true,
205
- compile: function (token) {
206
- const expression = token.match[1];
207
-
208
- token.stack = Reflect.apply(Twig.expression.compile, this, [{
209
- type: Twig.expression.type.expression,
210
- value: expression
211
- }]).stack;
212
-
213
- delete token.match;
214
- return token;
215
- },
216
- parse: function (token, context, chain) {
217
- let type = Reflect.apply(Twig.expression.parse, this, [token.stack, context]);
218
- let output = this.parse(token.output, context);
219
- let mirror = false;
220
-
221
- if (type.includes(":mirror")) {
222
- mirror = true;
223
- type = type.replace(":mirror", "")
224
- }
225
-
226
- return {
227
- chain: chain,
228
- output: `${mirror ? output : ""}${wrapPreCode(output, type)}`
229
- };
230
- }
231
- });
232
- Twig.exports.extendTag({
233
- type: "endcode",
234
- regex: /^endcode$/,
235
- next: [ ],
236
- open: false
237
- });
238
- }
239
- ]
53
+ vituum: {
54
+ pages: {
55
+ dir: './src/views'
56
+ }
57
+ },
58
+ posthtml: {
59
+ root: resolve(process.cwd(), 'src')
60
+ },
61
+ juice: {
62
+ paths: ['src/views/email']
63
+ },
64
+ tailwindcss: {},
65
+ send: {
66
+ host: 'smtp.newlogic.cz',
67
+ from: 'noreply@newlogic.cz',
68
+ user: 'noreply@newlogic.cz'
240
69
  },
241
70
  latte: {
242
- isStringFilter: (filename) => dirname(filename).endsWith('email'),
71
+ renderTransformedHtml: (filename) => dirname(filename).endsWith('email'),
243
72
  globals: {
244
73
  srcPath: resolve(process.cwd(), 'src'),
245
74
  templatesPath: resolve(process.cwd(), 'src/templates')
@@ -250,64 +79,115 @@ const defaultConfig = {
250
79
  }
251
80
  },
252
81
  filters: {
253
- json: async (input, name) => {
254
- return await parseMinifyHtml(input, name)
255
- },
82
+ json: resolve(process.cwd(), 'node_modules/@newlogic-digital/core/latte/JsonFilter.js'),
256
83
  code: 'node_modules/@newlogic-digital/core/latte/CodeFilter.php'
257
84
  },
258
- ignoredPaths: ['**/views/email/**/!(*.test).latte']
85
+ ignoredPaths: ['**/views/email/**/!(*.test).latte', '**/emails/!(*.test).latte']
259
86
  },
260
- postcssNesting: {
261
- noIsPseudoSelector: true
262
- }
87
+ twig: twigOptions
263
88
  }
264
89
 
265
- const integration = (userConfig = {}) => {
266
- userConfig = lodash.merge(defaultConfig, userConfig)
90
+ /**
91
+ * @param {import('@newlogic-digital/core/types').PluginUserConfig} options
92
+ * @returns [import('vite').Plugin]
93
+ */
94
+ const plugin = (options = {}) => {
95
+ options = merge(defaultOptions, options)
267
96
 
268
- return {
269
- config: {
270
- integrations: [posthtml(userConfig.posthtml), juice(userConfig.juice), twig(userConfig.twig), latte(userConfig.latte), {
271
- task: {
272
- name: 'emails',
273
- action: async () => {
274
- const emails = FastGlob.sync(`${resolve(process.cwd(), userConfig.emails.outputDir)}/**`).filter(entry => !entry.endsWith('test.html'))
275
- const emailsProd = emails.map(path => {
276
- return path.replace(resolve(process.cwd(), userConfig.emails.outputDir), resolve(process.cwd(), userConfig.emails.appDir)).replace('.html', '.latte')
277
- })
97
+ const templatesPlugins = []
278
98
 
279
- await Promise.all(emails.map((file, i) =>
280
- fse.move(file, emailsProd[i], { overwrite: true })
281
- ))
99
+ if (options.format.includes('twig')) {
100
+ templatesPlugins.push(twig(options.twig))
101
+ }
282
102
 
283
- console.info(`${chalk.cyan(`newlogic-core`)} ${chalk.green('all email files moved')}`)
284
- }
103
+ if (options.format.includes('latte')) {
104
+ templatesPlugins.push(latte(options.latte))
105
+ }
106
+
107
+ const plugins = [
108
+ vituum(options.vituum),
109
+ tailwindcss(options.tailwindcss),
110
+ posthtml(options.posthtml),
111
+ ...templatesPlugins,
112
+ juice(options.juice),
113
+ send(options.send),
114
+ posthtmlPrism
115
+ ]
116
+
117
+ return [{
118
+ name,
119
+ enforce: 'pre',
120
+ config (userConfig, userEnv) {
121
+ const isHttps = userConfig?.server?.https !== false &&
122
+ fs.existsSync(join(os.homedir(), `.ssh/${options.cert}.pem`)) &&
123
+ fs.existsSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`))
124
+
125
+ let defaultInput = [
126
+ './src/views/**/*.{json,latte,twig,liquid,njk,hbs,pug,html}',
127
+ '!./src/views/**/*.{latte,twig,liquid,njk,hbs,pug,html}.json',
128
+ './src/styles/*.{css,pcss,scss,sass,less,styl,stylus}',
129
+ './src/scripts/*.{js,ts,mjs}'
130
+ ]
131
+
132
+ if (!options.mode) {
133
+ options.mode = userEnv.mode
134
+ }
135
+
136
+ if (userEnv.mode === 'headless') {
137
+ userEnv.mode = 'production'
138
+
139
+ defaultInput = [
140
+ './src/styles/*.{css,pcss,scss,sass,less,styl,stylus}',
141
+ './src/scripts/*.{js,ts,mjs}'
142
+ ]
143
+ } else if (userEnv.mode === 'emails') {
144
+ userEnv.mode = 'production'
145
+
146
+ defaultInput = [
147
+ './src/views/email/**/*.{json,latte,twig,liquid,njk,hbs,pug,html}',
148
+ '!./src/views/email/**/*.{latte,twig,liquid,njk,hbs,pug,html}.json'
149
+ ]
150
+ }
151
+
152
+ userConfig.build = Object.assign({
153
+ manifest: true,
154
+ emptyOutDir: false,
155
+ modulePreload: false,
156
+ assetsInlineLimit: 0,
157
+ outDir: resolve(userConfig.root ?? process.cwd(), 'public'),
158
+ rollupOptions: {
159
+ input: defaultInput
285
160
  }
286
- }],
287
- plugins: [posthtmlPrism],
288
- server: {
289
- open: true,
290
- https: true,
291
- reload: file => (file.endsWith('.tpl') || file.endsWith('.latte')) && !file.includes('temp/')
292
- },
293
- templates: {
294
- format: userConfig.format
295
- },
296
- imports: {
297
- paths: ['./src/styles/**', './src/scripts/**', '!./src/styles/Utils/**']
298
- },
299
- vite: {
300
- server: {
301
- origin: fs.existsSync(resolve(process.cwd(), 'app/settings.php')) ? (fs.readFileSync(resolve(process.cwd(), 'app/settings.php')).toString().match(/VITE_URL = '(.+)';/) || [null, null])[1] : null
161
+ }, userConfig.build ?? {})
162
+
163
+ userConfig.server = Object.assign({
164
+ host: true,
165
+ fsServe: {
166
+ strict: false
302
167
  },
303
- css: {
304
- postcss: {
305
- plugins: [postcssImport, tailwindcssNesting(postcssNesting(userConfig.postcssNesting)), postcssCustomMedia, postcssCustomSelectors, tailwindcss(userConfig.tailwind), autoprefixer]
168
+ https: isHttps
169
+ ? {
170
+ key: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}-key.pem`)),
171
+ cert: fs.readFileSync(join(os.homedir(), `.ssh/${options.cert}.pem`))
306
172
  }
307
- }
173
+ : false
174
+ }, userConfig.server ?? {})
175
+ },
176
+ writeBundle: async () => {
177
+ if (options.mode === 'emails') {
178
+ const emails = FastGlob.sync(`${resolve(process.cwd(), options.emails.outputDir)}/**`).filter(entry => !entry.endsWith('test.html'))
179
+ const emailsProd = emails.map(path => {
180
+ return path.replace(resolve(process.cwd(), options.emails.outputDir), resolve(process.cwd(), options.emails.appDir)).replace('.html', '.latte')
181
+ })
182
+
183
+ await Promise.all(emails.map((file, i) =>
184
+ fse.move(file, emailsProd[i], { overwrite: true })
185
+ ))
186
+
187
+ console.info(`${pc.cyan('@newlogic-digital/core')} ${pc.green(`all email files were moved to ${relative(process.cwd(), options.emails.appDir)}`)}`)
308
188
  }
309
189
  }
310
- }
190
+ }, ...plugins]
311
191
  }
312
192
 
313
- export default integration
193
+ export default plugin
@@ -0,0 +1,7 @@
1
+ import parseMinifyHtml from '../src/minify.js'
2
+
3
+ const json = async (input, name) => {
4
+ return await parseMinifyHtml(input, name)
5
+ }
6
+
7
+ export default json
package/package.json CHANGED
@@ -1,30 +1,43 @@
1
1
  {
2
2
  "name": "@newlogic-digital/core",
3
3
  "type": "module",
4
- "version": "1.1.1",
4
+ "version": "2.0.0-alpha.10",
5
5
  "main": "index.js",
6
6
  "author": "New Logic Studio s.r.o.",
7
7
  "description": "Set of tools that can be used to create modern web applications",
8
8
  "license": "MIT",
9
9
  "scripts": {
10
- "npm-publish": "npm publish --tag next"
10
+ "tsc": "tsc",
11
+ "eslint": "eslint '**/*.js' --fix"
11
12
  },
12
13
  "dependencies": {
13
- "@vituum/posthtml": "^0.1.0",
14
- "@vituum/juice": "^0.1.5",
15
- "@vituum/twig": "^0.1.1",
16
- "@vituum/latte": "^0.1.1",
17
- "tailwindcss": "^3.3.1",
14
+ "@vituum/vite-plugin-posthtml": "^1.0.0-alpha.3",
15
+ "@vituum/vite-plugin-juice": "^1.0.0-alpha.2",
16
+ "@vituum/vite-plugin-latte": "^1.0.0-alpha.8",
17
+ "@vituum/vite-plugin-twig": "^1.0.0-alpha.6",
18
+ "@vituum/vite-plugin-tailwindcss": "^1.0.0-alpha.2",
19
+ "@vituum/vite-plugin-send": "^1.0.0-alpha.2",
20
+ "vituum": "^1.0.0-alpha.18",
21
+ "posthtml": "^0.16.6",
18
22
  "posthtml-prism": "^2.0.0",
19
23
  "prismjs": "^1.29.0",
20
24
  "html-minifier-terser": "^7.2.0",
21
25
  "lodash": "^4.17.21",
22
- "vituum": "^0.0.42"
26
+ "fast-glob": "^3.2.12",
27
+ "fs-extra": "^11.1.1",
28
+ "picocolors": "^1.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.3.1",
32
+ "eslint": "^8.43.0",
33
+ "eslint-config-standard": "^17.1.0",
34
+ "typescript": "^5.1.3",
35
+ "vite": "^4.3.9"
23
36
  },
24
37
  "files": [
25
38
  "latte",
26
39
  "index.js",
27
- "prism.js"
40
+ "src"
28
41
  ],
29
42
  "engines": {
30
43
  "node": ">=16.0.0",
package/src/minify.js ADDED
@@ -0,0 +1,22 @@
1
+ import minifier from 'html-minifier-terser'
2
+
3
+ const parseMinifyHtml = async (input, name) => {
4
+ const minify = await minifier.minify(input, {
5
+ collapseWhitespace: true,
6
+ collapseInlineTagWhitespace: false,
7
+ minifyCSS: true,
8
+ removeAttributeQuotes: true,
9
+ quoteCharacter: '\'',
10
+ minifyJS: true
11
+ })
12
+
13
+ if (name) {
14
+ return JSON.stringify({
15
+ [name]: minify
16
+ })
17
+ } else {
18
+ return JSON.stringify(minify)
19
+ }
20
+ }
21
+
22
+ export default parseMinifyHtml
package/src/twig.js ADDED
@@ -0,0 +1,174 @@
1
+ import fs from 'fs'
2
+ import { resolve } from 'path'
3
+ import parseMinifyHtml from './minify.js'
4
+
5
+ const wrapPreCode = (code, lang) => {
6
+ return `<pre class="language-${lang}"><code class="language-${lang}">${code}</code></pre>`
7
+ }
8
+
9
+ const stripIndent = (string) => {
10
+ const indent = () => {
11
+ const match = string.match(/^[ \t]*(?=\S)/gm)
12
+
13
+ if (!match) {
14
+ return 0
15
+ }
16
+
17
+ return match.reduce((r, a) => Math.min(r, a.length), Infinity)
18
+ }
19
+
20
+ if (indent() === 0) {
21
+ return string
22
+ }
23
+
24
+ const regex = new RegExp(`^[ \\t]{${indent()}}`, 'gm')
25
+
26
+ return string.replace(regex, '')
27
+ }
28
+
29
+ export default {
30
+ namespaces: {
31
+ src: resolve(process.cwd(), 'src'),
32
+ templates: resolve(process.cwd(), 'src/templates')
33
+ },
34
+ functions: {
35
+ pages: () => {
36
+ return fs.readdirSync('src/views').filter(file => fs.statSync('src/views/' + file).isFile())
37
+ },
38
+ fetch: (data) => {
39
+ if (typeof data !== 'undefined') {
40
+ if (data.indexOf('http') > -1) {
41
+ return data
42
+ } else {
43
+ let slash = data.indexOf('/') + 1
44
+ if (slash > 1) {
45
+ slash = 0
46
+ }
47
+
48
+ return fs.readFileSync(process.cwd() + '/' + data.substring(slash, data.length), 'utf8').toString()
49
+ }
50
+ }
51
+ },
52
+ randomColor: () => {
53
+ return '#' + Math.random().toString(16).slice(2, 8)
54
+ },
55
+ placeholder: (width, height) => {
56
+ const colors = ['333', '444', '666', '222', '777', '888', '111']
57
+ return 'https://via.placeholder.com/' + width + 'x' + height + '/' + colors[Math.floor(Math.random() * colors.length)] + '.webp'
58
+ },
59
+ lazy: (width, height) => {
60
+ const svg = encodeURIComponent(stripIndent('<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + width + ' ' + height + '"></svg>'))
61
+
62
+ return 'data:image/svg+xml;charset=UTF-8,' + svg
63
+ },
64
+ ratio: (width, height) => {
65
+ return (height / width) * 100
66
+ }
67
+ },
68
+ filters: {
69
+ asset: (url) => {
70
+ return url
71
+ },
72
+ rem: (value) => {
73
+ return `${value / 16}rem`
74
+ },
75
+ encode64: (path) => {
76
+ const svg = encodeURIComponent(stripIndent(path))
77
+
78
+ return 'data:image/svg+xml;charset=UTF-8,' + svg
79
+ },
80
+ exists: (path) => {
81
+ if (path.indexOf('/') === 0) {
82
+ path = path.slice(1)
83
+ }
84
+
85
+ return fs.existsSync(resolve(process.cwd(), path))
86
+ },
87
+ tel: (value) => {
88
+ return value.replace(/\s+/g, '').replace('(', '').replace(')', '')
89
+ }
90
+ },
91
+ extensions: [
92
+ (Twig) => {
93
+ Twig.exports.extendTag({
94
+ type: 'json',
95
+ regex: /^json\s+(.+)$|^json$/,
96
+ next: ['endjson'],
97
+ open: true,
98
+ compile: function (token) {
99
+ const expression = token.match[1] ?? '\'_null\''
100
+
101
+ token.stack = Reflect.apply(Twig.expression.compile, this, [{
102
+ type: Twig.expression.type.expression,
103
+ value: expression
104
+ }]).stack
105
+
106
+ delete token.match
107
+ return token
108
+ },
109
+ parse: async function (token, context, chain) {
110
+ const name = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
111
+ const output = this.parse(token.output, context)
112
+
113
+ if (name === '_null') {
114
+ return {
115
+ chain,
116
+ output: await parseMinifyHtml(output)
117
+ }
118
+ } else {
119
+ return {
120
+ chain,
121
+ output: await parseMinifyHtml(output, name)
122
+ }
123
+ }
124
+ }
125
+ })
126
+ Twig.exports.extendTag({
127
+ type: 'endjson',
128
+ regex: /^endjson$/,
129
+ next: [],
130
+ open: false
131
+ })
132
+ },
133
+ (Twig) => {
134
+ Twig.exports.extendTag({
135
+ type: 'code',
136
+ regex: /^code\s+(.+)$/,
137
+ next: ['endcode'], // match the type of the end tag
138
+ open: true,
139
+ compile: function (token) {
140
+ const expression = token.match[1]
141
+
142
+ token.stack = Reflect.apply(Twig.expression.compile, this, [{
143
+ type: Twig.expression.type.expression,
144
+ value: expression
145
+ }]).stack
146
+
147
+ delete token.match
148
+ return token
149
+ },
150
+ parse: function (token, context, chain) {
151
+ let type = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
152
+ const output = this.parse(token.output, context)
153
+ let mirror = false
154
+
155
+ if (type.includes(':mirror')) {
156
+ mirror = true
157
+ type = type.replace(':mirror', '')
158
+ }
159
+
160
+ return {
161
+ chain,
162
+ output: `${mirror ? output : ''}${wrapPreCode(output, type)}`
163
+ }
164
+ }
165
+ })
166
+ Twig.exports.extendTag({
167
+ type: 'endcode',
168
+ regex: /^endcode$/,
169
+ next: [],
170
+ open: false
171
+ })
172
+ }
173
+ ]
174
+ }
File without changes