nuxt-content-assets 0.5.1 → 0.5.2
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 +38 -21
- package/dist/module.d.ts +1 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +47 -38
- package/dist/runtime/options.d.ts +9 -0
- package/dist/runtime/options.mjs +14 -0
- package/dist/runtime/{server/plugins/plugin.mjs → plugin.mjs} +7 -4
- package/dist/runtime/utils/assets.d.ts +33 -0
- package/dist/runtime/utils/assets.mjs +42 -0
- package/dist/runtime/utils/content.d.ts +7 -0
- package/dist/runtime/utils/content.mjs +12 -0
- package/dist/runtime/utils/debug.d.ts +2 -0
- package/dist/runtime/utils/debug.mjs +7 -0
- package/dist/runtime/utils/index.d.ts +4 -0
- package/dist/runtime/utils/index.mjs +4 -0
- package/dist/runtime/utils/object.d.ts +10 -0
- package/dist/runtime/utils/object.mjs +22 -0
- package/package.json +1 -1
- /package/dist/runtime/{server/plugins/plugin.d.ts → plugin.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ To run the demo online, go to:
|
|
|
53
53
|
|
|
54
54
|
You can browse the demo files in:
|
|
55
55
|
|
|
56
|
-
-
|
|
56
|
+
- https://github.com/davestewart/nuxt-content-assets/tree/main/demo
|
|
57
57
|
|
|
58
58
|
To run the demo locally, clone the application and from the root, run:
|
|
59
59
|
|
|
@@ -74,7 +74,7 @@ Configure `nuxt.config.ts`:
|
|
|
74
74
|
```js
|
|
75
75
|
export default defineNuxtConfig({
|
|
76
76
|
modules: [
|
|
77
|
-
'nuxt-content-
|
|
77
|
+
'nuxt-content-assets', // make sure to add before content!
|
|
78
78
|
'@nuxt/content',
|
|
79
79
|
]
|
|
80
80
|
})
|
|
@@ -111,21 +111,28 @@ See the [configuration](#output) section for more options.
|
|
|
111
111
|
|
|
112
112
|
### Images
|
|
113
113
|
|
|
114
|
-
The module [optionally](#image-attributes)
|
|
114
|
+
The module can [optionally](#image-attributes) write `width`, `height` and `aspect-ratio` information to generated `<img>` tags:
|
|
115
115
|
|
|
116
116
|
```html
|
|
117
|
-
<img src="..." width="640" height="480">
|
|
117
|
+
<img src="..." width="640" height="480" style="aspect-ratio:640/480">
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
This
|
|
120
|
+
This can prevent content jumps on page load. If you add `attributes` only, include the following CSS in your app:
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
```css
|
|
123
|
+
img {
|
|
124
|
+
max-width: 100%;
|
|
125
|
+
height: auto;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If you use custom [ProseImg](https://content.nuxtjs.org/api/components/prose) components, you can use these values in your own markup:
|
|
123
130
|
|
|
124
131
|
```vue
|
|
125
132
|
<template>
|
|
126
|
-
<
|
|
127
|
-
<img :src="$attrs.src" :style="
|
|
128
|
-
</
|
|
133
|
+
<span class="image">
|
|
134
|
+
<img :src="$attrs.src" :style="$attrs.style" />
|
|
135
|
+
</span>
|
|
129
136
|
</template>
|
|
130
137
|
|
|
131
138
|
<script>
|
|
@@ -154,8 +161,8 @@ export default defineNuxtConfig({
|
|
|
154
161
|
// completely replace supported extensions
|
|
155
162
|
extensions: 'png jpg',
|
|
156
163
|
|
|
157
|
-
//
|
|
158
|
-
|
|
164
|
+
// use aspect-ratio rather than attributes
|
|
165
|
+
imageSize: 'ratio',
|
|
159
166
|
|
|
160
167
|
// print debug messages to the console
|
|
161
168
|
debug: true,
|
|
@@ -168,7 +175,7 @@ export default defineNuxtConfig({
|
|
|
168
175
|
The output path can be customised using a template string:
|
|
169
176
|
|
|
170
177
|
```
|
|
171
|
-
assets/
|
|
178
|
+
assets/content/[name]-[hash].[ext]
|
|
172
179
|
```
|
|
173
180
|
|
|
174
181
|
The first part of the path should be public root-relative folder:
|
|
@@ -177,7 +184,7 @@ The first part of the path should be public root-relative folder:
|
|
|
177
184
|
assets/img/content
|
|
178
185
|
```
|
|
179
186
|
|
|
180
|
-
The optional second part of the path indicates
|
|
187
|
+
The optional second part of the path indicates the relative location of each image:
|
|
181
188
|
|
|
182
189
|
| Token | Description | Example |
|
|
183
190
|
|-------------|--------------------------------------------|--------------------|
|
|
@@ -196,15 +203,15 @@ For example:
|
|
|
196
203
|
| `assets/img/[name]-[hash].[ext]` | `assets/img/featured-9M00N4l9A0.jpg` |
|
|
197
204
|
| `content/[hash].[ext]` | `content/9M00N4l9A0.jpg` |
|
|
198
205
|
|
|
199
|
-
Note that the module defaults to
|
|
206
|
+
Note that the module defaults to:
|
|
200
207
|
|
|
201
208
|
```
|
|
202
|
-
/assets/content/[
|
|
209
|
+
/assets/content/[folder]/[file]
|
|
203
210
|
```
|
|
204
211
|
|
|
205
212
|
### Extensions
|
|
206
213
|
|
|
207
|
-
You can add or replace supported extensions if you need to:
|
|
214
|
+
You can add (or replace) supported extensions if you need to:
|
|
208
215
|
|
|
209
216
|
To add extensions, use `additionalExtensions`:
|
|
210
217
|
|
|
@@ -214,26 +221,36 @@ To add extensions, use `additionalExtensions`:
|
|
|
214
221
|
}
|
|
215
222
|
```
|
|
216
223
|
|
|
217
|
-
To
|
|
224
|
+
To replace extensions, use `extensions`:
|
|
218
225
|
|
|
219
226
|
```ts
|
|
220
227
|
{
|
|
221
|
-
|
|
228
|
+
extensions: 'png jpg' // serve png and jpg files only
|
|
222
229
|
}
|
|
223
230
|
```
|
|
224
231
|
|
|
225
232
|
### Image attributes
|
|
226
233
|
|
|
227
|
-
|
|
234
|
+
You can add image size hints to the generated images.
|
|
235
|
+
|
|
236
|
+
To add `style` aspect-ratio:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
{
|
|
240
|
+
imageSize: 'style'
|
|
241
|
+
}
|
|
242
|
+
```
|
|
228
243
|
|
|
229
|
-
|
|
244
|
+
To add `width` and `height` attributes:
|
|
230
245
|
|
|
231
246
|
```ts
|
|
232
247
|
{
|
|
233
|
-
|
|
248
|
+
imageSize: 'attrs'
|
|
234
249
|
}
|
|
235
250
|
```
|
|
236
251
|
|
|
252
|
+
You can even add both if you need to.
|
|
253
|
+
|
|
237
254
|
## Development
|
|
238
255
|
|
|
239
256
|
Should you wish to develop the project, the scripts are:
|
package/dist/module.d.ts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -6,8 +6,37 @@ import * as Path from 'path';
|
|
|
6
6
|
import Path__default from 'path';
|
|
7
7
|
import { hash } from 'ohash';
|
|
8
8
|
|
|
9
|
+
function getSources(sources) {
|
|
10
|
+
return Object.keys(sources).reduce((output, key) => {
|
|
11
|
+
const source = sources[key];
|
|
12
|
+
if (source) {
|
|
13
|
+
const { driver, base } = source;
|
|
14
|
+
if (driver === "fs") {
|
|
15
|
+
output[key] = base;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return output;
|
|
19
|
+
}, {});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaults = {
|
|
23
|
+
assetsDir: "assets/content",
|
|
24
|
+
assetsPattern: "[folder]/[file]"
|
|
25
|
+
};
|
|
26
|
+
const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
|
|
27
|
+
const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
|
|
28
|
+
const fileExtensions = matchWords("pdf doc docx xls xlsx ppt pptx odp key");
|
|
29
|
+
const extensions = [
|
|
30
|
+
...imageExtensions,
|
|
31
|
+
...mediaExtensions,
|
|
32
|
+
...fileExtensions
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const moduleName = "nuxt-content-assets";
|
|
36
|
+
const moduleKey = "content-assets";
|
|
37
|
+
|
|
9
38
|
function log(...data) {
|
|
10
|
-
console.info(`[${
|
|
39
|
+
console.info(`[${moduleKey}]`, ...data);
|
|
11
40
|
}
|
|
12
41
|
|
|
13
42
|
function matchWords(value) {
|
|
@@ -39,48 +68,22 @@ const replacers = {
|
|
|
39
68
|
hash: (src) => hash({ src })
|
|
40
69
|
};
|
|
41
70
|
|
|
42
|
-
const name = "content-assets";
|
|
43
|
-
const defaults = {
|
|
44
|
-
assetsDir: "assets/content",
|
|
45
|
-
assetsPattern: "[name]-[hash].[ext]"
|
|
46
|
-
};
|
|
47
|
-
const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
|
|
48
|
-
const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
|
|
49
|
-
const fileExtensions = matchWords("pdf doc docx xls xlsx ppt pptx odp key");
|
|
50
|
-
const extensions = [
|
|
51
|
-
...imageExtensions,
|
|
52
|
-
...mediaExtensions,
|
|
53
|
-
...fileExtensions
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
function getSources(sources) {
|
|
57
|
-
return Object.keys(sources).reduce((output, key) => {
|
|
58
|
-
const source = sources[key];
|
|
59
|
-
if (source) {
|
|
60
|
-
const { driver, base } = source;
|
|
61
|
-
if (driver === "fs") {
|
|
62
|
-
output[key] = base;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return output;
|
|
66
|
-
}, {});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
71
|
const resolve = createResolver(import.meta.url).resolve;
|
|
70
72
|
const module = defineNuxtModule({
|
|
71
73
|
meta: {
|
|
72
|
-
name
|
|
74
|
+
name: moduleName,
|
|
75
|
+
configKey: moduleKey
|
|
73
76
|
},
|
|
74
77
|
defaults: {
|
|
75
78
|
output: `${defaults.assetsDir}/${defaults.assetsPattern}`,
|
|
76
79
|
extensions: "",
|
|
77
80
|
additionalExtensions: "",
|
|
78
|
-
|
|
81
|
+
imageSize: "",
|
|
79
82
|
debug: false
|
|
80
83
|
},
|
|
81
84
|
setup(options, nuxt) {
|
|
82
85
|
var _a;
|
|
83
|
-
const pluginPath = resolve("./runtime/
|
|
86
|
+
const pluginPath = resolve("./runtime/plugin");
|
|
84
87
|
const buildPath = nuxt.options.buildDir;
|
|
85
88
|
const cachePath = Path.resolve(buildPath, "content-assets");
|
|
86
89
|
if (options.debug) {
|
|
@@ -116,16 +119,22 @@ const module = defineNuxtModule({
|
|
|
116
119
|
function getAssetConfig(pattern, src, dir) {
|
|
117
120
|
let width = void 0;
|
|
118
121
|
let height = void 0;
|
|
119
|
-
|
|
122
|
+
let ratio = "";
|
|
123
|
+
if (options.imageSize && isImage(src)) {
|
|
120
124
|
const size = getImageSize(src);
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
if (options.imageSize.includes("style")) {
|
|
126
|
+
ratio = `${size.width}/${size.height}`;
|
|
127
|
+
}
|
|
128
|
+
if (options.imageSize.includes("attrs")) {
|
|
129
|
+
width = size.width;
|
|
130
|
+
height = size.height;
|
|
131
|
+
}
|
|
123
132
|
}
|
|
124
133
|
const id = Path.join(Path.basename(dir), Path.relative(dir, src)).replaceAll("/", ":");
|
|
125
134
|
const file = interpolatePattern(pattern, src, dir);
|
|
126
135
|
const trg = Path.join(cachePath, assetsDir, file);
|
|
127
136
|
const rel = Path.join("/", assetsDir, file);
|
|
128
|
-
return { id, file, trg, rel, width, height };
|
|
137
|
+
return { id, file, trg, rel, width, height, ratio };
|
|
129
138
|
}
|
|
130
139
|
const publicFolder = Path.join(cachePath, assetsDir);
|
|
131
140
|
const sourceFolders = Object.values(sources);
|
|
@@ -159,15 +168,15 @@ ${paths.join("\n")}
|
|
|
159
168
|
`export const assets = ${JSON.stringify(assets)}`,
|
|
160
169
|
`export const sources = ${JSON.stringify(sources)}`
|
|
161
170
|
].join("\n");
|
|
162
|
-
nuxt.options.alias[`#${
|
|
163
|
-
filename: `${
|
|
171
|
+
nuxt.options.alias[`#${moduleName}`] = addTemplate({
|
|
172
|
+
filename: `${moduleName}.mjs`,
|
|
164
173
|
getContents: () => virtualConfig
|
|
165
174
|
}).dst;
|
|
166
175
|
nuxt.hook("nitro:config", async (config) => {
|
|
167
176
|
config.plugins || (config.plugins = []);
|
|
168
177
|
config.plugins.push(pluginPath);
|
|
169
178
|
config.virtual || (config.virtual = {});
|
|
170
|
-
config.virtual[`#${
|
|
179
|
+
config.virtual[`#${moduleName}`] = virtualConfig;
|
|
171
180
|
config.publicAssets || (config.publicAssets = []);
|
|
172
181
|
config.publicAssets.push({
|
|
173
182
|
dir: cachePath
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const defaults: {
|
|
2
|
+
assetsDir: string;
|
|
3
|
+
assetsPattern: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const imageExtensions: RegExpMatchArray | [];
|
|
6
|
+
export declare const mediaExtensions: RegExpMatchArray | [];
|
|
7
|
+
export declare const fileExtensions: RegExpMatchArray | [];
|
|
8
|
+
export declare const extensions: string[];
|
|
9
|
+
export declare const tags: string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { matchWords } from "./utils/assets.mjs";
|
|
2
|
+
export const defaults = {
|
|
3
|
+
assetsDir: "assets/content",
|
|
4
|
+
assetsPattern: "[folder]/[file]"
|
|
5
|
+
};
|
|
6
|
+
export const imageExtensions = matchWords("png jpg jpeg gif svg webp ico");
|
|
7
|
+
export const mediaExtensions = matchWords("mp3 m4a wav mp4 mov webm ogg avi flv avchd");
|
|
8
|
+
export const fileExtensions = matchWords("pdf doc docx xls xlsx ppt pptx odp key");
|
|
9
|
+
export const extensions = [
|
|
10
|
+
...imageExtensions,
|
|
11
|
+
...mediaExtensions,
|
|
12
|
+
...fileExtensions
|
|
13
|
+
];
|
|
14
|
+
export const tags = ["img", "video", "audio", "source", "embed", "iframe", "a"];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import Path from "path";
|
|
2
2
|
import { visit } from "unist-util-visit";
|
|
3
|
-
import {
|
|
4
|
-
import { tags } from "
|
|
5
|
-
import {
|
|
3
|
+
import { isValidAsset, walk } from "./utils/index.mjs";
|
|
4
|
+
import { tags } from "./options.mjs";
|
|
5
|
+
import { assets, sources } from "#nuxt-content-assets";
|
|
6
6
|
function getDocPath(id) {
|
|
7
7
|
const parts = id.split(":");
|
|
8
8
|
const key = parts.shift();
|
|
@@ -29,7 +29,7 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
29
29
|
}, filter);
|
|
30
30
|
visit(file.body, (n) => tags.includes(n.tag), (node) => {
|
|
31
31
|
if (node.props.src) {
|
|
32
|
-
const { rel, width, height } = getAsset(absDoc, node.props.src);
|
|
32
|
+
const { rel, width, height, ratio } = getAsset(absDoc, node.props.src);
|
|
33
33
|
if (rel) {
|
|
34
34
|
node.props.src = rel;
|
|
35
35
|
}
|
|
@@ -37,6 +37,9 @@ export default defineNitroPlugin(async (nitroApp) => {
|
|
|
37
37
|
node.props.width = width;
|
|
38
38
|
node.props.height = height;
|
|
39
39
|
}
|
|
40
|
+
if (ratio) {
|
|
41
|
+
node.props.style = `aspect-ratio:${ratio}`;
|
|
42
|
+
}
|
|
40
43
|
} else if (node.tag === "a") {
|
|
41
44
|
if (node.props.href) {
|
|
42
45
|
const { rel } = getAsset(absDoc, node.props.href);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get matched words from a string
|
|
3
|
+
*/
|
|
4
|
+
export declare function matchWords(value: string): RegExpMatchArray | [];
|
|
5
|
+
/**
|
|
6
|
+
* Test path to be relative
|
|
7
|
+
*/
|
|
8
|
+
export declare function isRelative(path: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Test path for image extension
|
|
11
|
+
*/
|
|
12
|
+
export declare function isImage(path: string): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Test path for asset extension
|
|
15
|
+
*/
|
|
16
|
+
export declare function isAsset(path: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Test if value is a valid asset
|
|
19
|
+
*/
|
|
20
|
+
export declare function isValidAsset(value?: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Interpolate assets path pattern
|
|
23
|
+
*
|
|
24
|
+
* @param pattern A path pattern with tokens
|
|
25
|
+
* @param src The absolute path to a src asset
|
|
26
|
+
* @param dir The absolute path to its containing folder
|
|
27
|
+
* @param warn An optional flag to warn for unknown tokens
|
|
28
|
+
*/
|
|
29
|
+
export declare function interpolatePattern(pattern: string, src: string, dir: string, warn?: boolean): any;
|
|
30
|
+
/**
|
|
31
|
+
* Hash of replacer functions
|
|
32
|
+
*/
|
|
33
|
+
export declare const replacers: Record<string, Function>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Path from "path";
|
|
2
|
+
import { hash as ohash } from "ohash";
|
|
3
|
+
import { extensions, imageExtensions } from "../options.mjs";
|
|
4
|
+
import { log } from "./debug.mjs";
|
|
5
|
+
export function matchWords(value) {
|
|
6
|
+
return value.match(/\w+/g) || [];
|
|
7
|
+
}
|
|
8
|
+
export function isRelative(path) {
|
|
9
|
+
return !(path.startsWith("http") || Path.isAbsolute(path));
|
|
10
|
+
}
|
|
11
|
+
export function isImage(path) {
|
|
12
|
+
const ext = Path.extname(path).substring(1);
|
|
13
|
+
return imageExtensions.includes(ext);
|
|
14
|
+
}
|
|
15
|
+
export function isAsset(path) {
|
|
16
|
+
const ext = Path.extname(path).substring(1);
|
|
17
|
+
return extensions.includes(ext);
|
|
18
|
+
}
|
|
19
|
+
export function isValidAsset(value) {
|
|
20
|
+
return typeof value === "string" && isAsset(value) && isRelative(value);
|
|
21
|
+
}
|
|
22
|
+
export function interpolatePattern(pattern, src, dir, warn = false) {
|
|
23
|
+
return Path.join(pattern.replace(/\[\w+]/g, (match) => {
|
|
24
|
+
const name = match.substring(1, match.length - 1);
|
|
25
|
+
const fn = replacers[name];
|
|
26
|
+
if (fn) {
|
|
27
|
+
return fn(src, dir);
|
|
28
|
+
}
|
|
29
|
+
if (warn) {
|
|
30
|
+
log(`Unknown output token ${match}`, true);
|
|
31
|
+
}
|
|
32
|
+
return match;
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
export const replacers = {
|
|
36
|
+
folder: (src, dir) => Path.dirname(src.replace(dir, "")),
|
|
37
|
+
file: (src) => Path.basename(src),
|
|
38
|
+
name: (src) => Path.basename(src, Path.extname(src)),
|
|
39
|
+
extname: (src) => Path.extname(src),
|
|
40
|
+
ext: (src) => Path.extname(src).substring(1),
|
|
41
|
+
hash: (src) => ohash({ src })
|
|
42
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function getSources(sources) {
|
|
2
|
+
return Object.keys(sources).reduce((output, key) => {
|
|
3
|
+
const source = sources[key];
|
|
4
|
+
if (source) {
|
|
5
|
+
const { driver, base } = source;
|
|
6
|
+
if (driver === "fs") {
|
|
7
|
+
output[key] = base;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return output;
|
|
11
|
+
}, {});
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Callback = (value: any, parent?: any, key?: string | number) => void;
|
|
2
|
+
export type Filter = (value: any, key?: string | number) => boolean | void;
|
|
3
|
+
/**
|
|
4
|
+
* Walk an object structure
|
|
5
|
+
*
|
|
6
|
+
* @param node
|
|
7
|
+
* @param callback
|
|
8
|
+
* @param filter
|
|
9
|
+
*/
|
|
10
|
+
export declare function walk(node: any, callback: Callback, filter?: Filter): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function walk(node, callback, filter) {
|
|
2
|
+
function visit(node2, callback2, parent, key) {
|
|
3
|
+
if (filter) {
|
|
4
|
+
const result = filter(node2, key);
|
|
5
|
+
if (result === false) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
if (Array.isArray(node2)) {
|
|
10
|
+
node2.forEach((value, index) => {
|
|
11
|
+
visit(value, callback2, node2, index);
|
|
12
|
+
});
|
|
13
|
+
} else if (typeof node2 === "object" && node2 !== null) {
|
|
14
|
+
Object.keys(node2).forEach((key2) => {
|
|
15
|
+
visit(node2[key2], callback2, node2, key2);
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
callback2(node2, parent, key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
visit(node, callback);
|
|
22
|
+
}
|
package/package.json
CHANGED
|
File without changes
|