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

Sign up to get free protection for your applications and to get access to all the features.
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