Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lazy loading to md image #337

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Current (in progress)

- Nothing yet
- force image lazy loading to markdown content [#337](https://github.com/etalab/udata-front/pull/337)

## 3.2.12 (2023-12-15)

Expand Down
17 changes: 15 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"cypress-axe": "^1.5.0",
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
"dompurify": "^3.0.6",
"es-module-shims": "^1.7.0",
"glob": "^10.2.2",
"less": "^4.1.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import MarkdownViewer from './MarkdownViewer.vue';

export default {
title: 'Components/MarkdownViewer',
component: MarkdownViewer,
};

const args = {
content: `# h1 Heading 8-)
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Horizontal Rules
___
---
***
## Typographic replacements
Enable typographer option to see result.
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
test.. test... test..... test?..... test!....
!!!!!! ???? ,, -- ---
<details>
<summary>I have keys but no doors. I have space but no room. You can enter but can’t leave. What am I?</summary>
A keyboard.
</details>
"Smartypants, double quotes" and 'single quotes'`,
};

export const MarkdownViewerWithContent = {
render: (args) => ({
components: { MarkdownViewer },
setup() {
return { args };
},
template: '<MarkdownViewer v-bind="args"/>',
}),
args,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<div class="markdown" ref="markdownRef" v-html="markdown(mdContent)">
</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { markdown } from "../../helpers";
import { onMounted } from "vue";

const props = defineProps<{ content?: string }>();
const markdownRef = ref<HTMLDivElement | null>(null);
const mdContent = ref(props.content ?? '');
onMounted(() => {
if(!props.content) {
mdContent.value = JSON.parse(markdownRef.value?.dataset.content ?? '');
}
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,18 @@ export const NoReadMoreOnSmallContent = {
}),
args: {},
};

export const ReadMoreWithParagraph = {
render: (args) => ({
components: { ReadMore },
setup() {
return { args };
},
template: ` <ReadMore v-bind="args">
<div class="white-space-pre-wrap overflow-wrap-anywhere">
<div><p>Dès septembre 2021, l’ensemble du 7e arrondissement de Lyon va être équipé de bornes d’apport volontaire de déchets alimentaires. En 2022, le déploiement se poursuivra dans des territoires de l’est et de l’ouest lyonnais, pour une couverture de l’ensemble des villes et centre-bourg d’ici fin 2023.</p></div>
</div>
</ReadMore>`,
}),
args: {},
};
2 changes: 2 additions & 0 deletions udata_front/theme/gouvfr/assets/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as dsfr from "@gouvfr/dsfr/dist/dsfr/dsfr.module";
import Chart from "./components/charts/chart.vue";
import Threads from "./components/discussions/Threads.vue";
import ThreadCreate from "./components/discussions/ThreadCreate/ThreadCreate.vue";
import MarkdownViewer from "./components/Markdown/MarkdownViewer.vue";
import MenuSearch from "./components/search/menu-search.vue";
import Search from "./components/search/search.vue";
import FeaturedButton from './components/utils/featured.vue';
Expand Down Expand Up @@ -58,6 +59,7 @@ const configAndMountApp = (el) => {
app.component("search", Search);
app.component("follow-button", FollowButton);
app.component("featured-button", FeaturedButton);
app.component("markdown-viewer", MarkdownViewer);
app.component("read-more", ReadMore);
app.component("request-membership", RequestMembership);
app.component("dataset-resources", Resources);
Expand Down
25 changes: 22 additions & 3 deletions udata_front/theme/gouvfr/assets/js/markdown.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import markdownit from 'markdown-it';
import purify from 'dompurify';
import { markdown as markdownConfig } from "./config";

const markdown = markdownit({
html: false,
html: true,
linkify: true,
typographer: true,
breaks: true,
});

// Disable mail linkification
markdown.linkify.add('mailto:', null)
markdown.linkify.set({ fuzzyEmail: false });

markdown.use(function(md) {
// Add `rel="ugc "` to links
md.renderer.rules.link_open = function(tokens, idx, options, env, self) {
const link_open = tokens[idx];
link_open.attrs.push(['rel','ugc ']);
Expand All @@ -29,8 +32,24 @@ markdown.use(function(md) {
s_close.tag = 'del';
return self.renderToken(tokens, idx, options);
};
// Add `loading="lazy"` to all images
md.renderer.rules.image = function (tokens, idx, options, env, slf) {
var token = tokens[idx];
token.attrs[token.attrIndex('alt')][1] = slf.renderInlineAsText(token.children, options, env);
// this is the line of code responsible for an additional 'loading' attribute
token.attrSet('loading', 'lazy');
return slf.renderToken(tokens, idx, options);
};
});

export default function(text) {
return markdown.render(text).trim();
const attributes = Object.values(markdownConfig.attributes).flat();
const uniqueAttributes = [...new Set(attributes)];
const content = markdown.render(text).trim();
return purify.sanitize(content, {
ALLOWED_TAGS: markdownConfig.tags,
ALLOWED_ATTR: uniqueAttributes,
ALLOW_DATA_ATTR: false,
USE_PROFILES: { html: true },
});
}
3 changes: 2 additions & 1 deletion udata_front/theme/gouvfr/templates/dataset/display.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ <h1 class="fr-mb-5v fr-h3">
</h1>
<h2 id="description" class="subtitle fr-mb-1w">{{ _('Description') }}</h2>
<read-more max-height="description" class="fr-mb-n1w">
{{ dataset.description|markdown }}
<markdown-viewer data-content="{{dataset.description|to_json}}"></markdown-viewer>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep this idea, we should apply the same logic for other markdown contents? In particular, I'm thinking of pages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed the idea to have a unified viewer for all contents. This could allow us to use DSFR tables everywhere for example instead of our custom ones.

</read-more>
<noscript>{{ dataset.description | markdown }}</noscript>
</section>
<section class="fr-col-12 fr-col-md-4" data-read-more-max-height="description">
{% if sysadmin %}
Expand Down