Compare commits

...

12 Commits

Author SHA1 Message Date
d3d0e7c44d Upgrade to Vue 3 2023-09-24 11:57:57 +02:00
d9b5af7388 Improve warning and embed block 2021-01-06 17:26:46 +01:00
cefdf76045 Improve attaches block 2020-11-09 17:04:46 +01:00
92933de4bd Adjustments to attaches block 2020-11-09 10:18:20 +01:00
5dd1b7c0ef Optimize for server side rendering 2020-11-02 13:11:22 +01:00
945bef0523 Update for lazy loading images 2020-10-20 18:42:43 +02:00
fcf12f7dcf Add anchor ids to headers 2020-10-11 12:22:14 +02:00
91b7922464 Add math block 2020-09-30 22:06:01 +02:00
540333d431 Improve and fix button block 2020-09-28 22:06:47 +02:00
7639ed84b3 Add button block 2020-09-27 15:09:58 +02:00
1cb252b952 Allow html tags in headers and lists 2020-09-26 19:06:30 +02:00
2684c5d58d Fix link block image 2020-08-24 22:23:33 +02:00
31 changed files with 2706 additions and 17719 deletions

View File

@@ -1,14 +1,44 @@
module.exports = {
root: true,
env: {
node: true
node: true,
},
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint"
parser: 'babel-eslint',
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
}
semi: [
'error',
'always',
],
quotes: [
2,
'single',
],
'comma-dangle': [
'error',
{
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'always-multiline',
'exports': 'always-multiline',
'functions': 'never',
},
],
'space-before-function-paren': [
'error',
'never',
],
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'max-len': ['warn', {
'code': 100,
'ignoreStrings': true,
}],
},
extends: [
'plugin:vue/recommended',
'eslint:recommended',
],
};

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.DS_Store
node_modules
dist
# local env files
.env.local

View File

@@ -1,3 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
presets: ['@vue/cli-plugin-babel/preset'],
};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10
dist/demo.html vendored
View File

@@ -1,10 +0,0 @@
<meta charset="utf-8">
<title>block-renderer demo</title>
<script src="./block-renderer.umd.js"></script>
<link rel="stylesheet" href="./block-renderer.css">
<script>
console.log(block-renderer)
</script>

13664
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +1,46 @@
{
"name": "block-renderer",
"description": "Content renderer for JSON blocks from Editor.js",
"version": "0.1.9",
"version": "0.7.7",
"author": "KingOfDog <info@kingofdog.de>",
"repository": {
"type": "git",
"url": "https://git.kingofdog.de/KingOfDog/block-renderer"
},
"homepage": "https://git.kingofdog.de/KingOfDog/block-renderer",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build-package": "vue-cli-service build --target lib --name block-renderer ./src/main.js",
"lint": "vue-cli-service lint"
"type": "module",
"files": [
"dist"
],
"main": "./dist/block-renderer.umd.js",
"module": "./dist/block-renderer.es.js",
"exports": {
".": {
"import": "./dist/block-renderer.es.js",
"require": "./dist/block-renderer.umd.js"
},
"./dist/block-renderer.css": {
"import": "./dist/block-renderer.css",
"require": "./dist/block-renderer.css"
}
},
"types": "./dist/main.d.ts",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"main": "./dist/block-renderer.common.js",
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11"
"katex": "^0.16.8",
"vue": "^3.3.4"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^1.19.1",
"vue-template-compiler": "^2.6.11"
"@vitejs/plugin-vue": "^4.0.0",
"path": "^0.12.7",
"rollup-plugin-typescript2": "^0.34.1",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vite-plugin-dts": "^1.7.1",
"vue-tsc": "^1.0.11"
}
}

View File

@@ -1,83 +1,84 @@
<template>
<div class="attaches-block">
<div
:data-extension="data.file.extension"
:style="{ color: color }"
class="attaches-block-file-icon"
>
<a :href="url" target="blank">
<div :data-extension="data.file.extension" class="attaches-block-file-icon" :style="{ color: color }">
<svg width="32" height="40">
<path
v-if="color"
d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z"
/>
<path v-if="color"
d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z" />
<g v-else fill="#A8ACB8" fill-rule="evenodd">
<path
fill-rule="nonzero"
d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z"
/>
<path fill-rule="nonzero"
d="M17 0l15 14V3v34a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3V3a3 3 0 0 1 3-3h20-6zm0 2H3a1 1 0 0 0-1 1v34a1 1 0 0 0 1 1h26a1 1 0 0 0 1-1V14H17V2zm2 10h7.926L19 4.602V12z" />
<path d="M7 22h18v2H7zm0 4h18v2H7zm0 4h18v2H7z" />
</g>
</svg>
</div>
</a>
<div class="attaches-block-file-info">
<p class="attaches-block-title">{{ data.file.name }}</p>
<p class="attaches-block-size">{{ data.file.size | byteFormatter }}</p>
<a :href="url" target="blank">
<p class="attaches-block-title">{{ data.title }}</p>
</a>
<p class="attaches-block-size">
{{ fileSize }}
</p>
</div>
<a :href="data.file.url" target="_blank" class="attaches-block-file-download-button">
<a :href="url" target="_blank" class="attaches-block-file-download-button">
<svg xmlns="http://www.w3.org/2000/svg" width="17pt" height="17pt" viewBox="0 0 17 17">
<path
d="M9.457 8.945V2.848A.959.959 0 0 0 8.5 1.89a.959.959 0 0 0-.957.957v6.097L4.488 5.891a.952.952 0 0 0-1.351 0 .952.952 0 0 0 0 1.351l4.687 4.688a.955.955 0 0 0 1.352 0l4.687-4.688a.952.952 0 0 0 0-1.351.952.952 0 0 0-1.351 0zM3.59 14.937h9.82a.953.953 0 0 0 .953-.957.952.952 0 0 0-.953-.953H3.59a.952.952 0 0 0-.953.953c0 .532.425.957.953.957zm0 0"
fill-rule="evenodd"
/>
fill-rule="evenodd" />
</svg>
</a>
</div>
</template>
<script>
import byteFormatter from "../filters/byteFormatter";
import byteFormatter from '../filters/byteFormatter';
const extensions = {
doc: "#3e74da",
docx: "#3e74da",
odt: "#3e74da",
pdf: "#d47373",
rtf: "#656ecd",
tex: "#5a5a5b",
txt: "#5a5a5b",
pptx: "#e07066",
ppt: "#e07066",
mp3: "#eab456",
mp4: "#f676a6",
xls: "#3f9e64",
html: "#2988f0",
htm: "#2988f0",
png: "#f676a6",
jpg: "#f67676",
jpeg: "#f67676",
gif: "#f6af76",
zip: "#4f566f",
rar: "#4f566f",
exe: "#e26f6f",
svg: "#bf5252",
key: "#e07066",
sketch: "#df821c",
ai: "#df821c",
psd: "#388ae5",
dmg: "#e26f6f",
json: "#2988f0",
csv: "#3f9e64"
doc: '#3e74da',
docx: '#3e74da',
odt: '#3e74da',
pdf: '#d47373',
rtf: '#656ecd',
tex: '#5a5a5b',
txt: '#5a5a5b',
pptx: '#e07066',
ppt: '#e07066',
mp3: '#eab456',
mp4: '#f676a6',
xls: '#3f9e64',
html: '#2988f0',
htm: '#2988f0',
png: '#f676a6',
jpg: '#f67676',
jpeg: '#f67676',
gif: '#f6af76',
zip: '#4f566f',
rar: '#4f566f',
exe: '#e26f6f',
svg: '#bf5252',
key: '#e07066',
sketch: '#df821c',
ai: '#df821c',
psd: '#388ae5',
dmg: '#e26f6f',
json: '#2988f0',
csv: '#3f9e64',
};
export default {
filters: {
byteFormatter
},
props: ["data"],
props: ['data'],
inject: ['$baseFileUrl'],
computed: {
color() {
return extensions[this.data.file.extension];
}
return extensions[this.data.file.extension.toLowerCase()];
},
url() {
return `${this.$baseFileUrl}/${this.data.file.id}?name=${this.data.file.name}`;
},
fileSize() {
return byteFormatter(this.data.file.size)
}
},
};
</script>

View File

@@ -1,70 +1,50 @@
<template>
<div>
<component
v-for="(block, index) in blocks"
:key="index"
:is="block.component"
:data="block.data"
/>
<component :is="block.component" v-for="(block, index) in blocks" :key="index" :data="block.data" />
</div>
</template>
<script>
import AttachesBlock from "./AttachesBlock";
import EmbedBlock from "./EmbedBlock";
import HeaderBlock from "./HeaderBlock";
import ImageBlock from "./ImageBlock";
import LinkBlock from "./LinkBlock";
import ListBlock from "./ListBlock";
import ParagraphBlock from "./ParagraphBlock";
import QuoteBlock from "./QuoteBlock";
import WarningBlock from "./WarningBlock";
import AttachesBlock from './AttachesBlock.vue';
import ButtonBlock from './ButtonBlock.vue';
import EmbedBlock from './EmbedBlock.vue';
import HeaderBlock from './HeaderBlock.vue';
import ImageBlock from './ImageBlock.vue';
import LinkBlock from './LinkBlock.vue';
import ListBlock from './ListBlock.vue';
import MathBlock from './MathBlock.vue';
import ParagraphBlock from './ParagraphBlock.vue';
import QuoteBlock from './QuoteBlock.vue';
import WarningBlock from './WarningBlock.vue';
const componentTypes = {
attaches: AttachesBlock,
button: ButtonBlock,
embed: EmbedBlock,
header: HeaderBlock,
image: ImageBlock,
link: LinkBlock,
linkEmbed: LinkBlock,
list: ListBlock,
math: MathBlock,
paragraph: ParagraphBlock,
quote: QuoteBlock,
warning: WarningBlock
warning: WarningBlock,
};
export default {
props: ["content"],
data() {
return {
blocks: []
};
},
mounted() {
if (this.content) {
this.prepareBlocks();
}
},
methods: {
prepareBlocks() {
props: ['content'],
computed: {
blocks() {
if (!this.content.blocks) {
return;
return [];
}
this.blocks = this.content.blocks.map(block => ({
return this.content.blocks.map(block => ({
component: componentTypes[block.type],
type: block.type,
data: block.data
data: block.data,
}));
}
},
watch: {
content() {
if (this.content) {
this.prepareBlocks();
}
}
}
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,15 @@
<template>
<div class="button-block">
<a
:href="data.url"
target="_blank"
v-html="data.label"
/>
</div>
</template>
<script>
export default {
props: ['data'],
};
</script>

View File

@@ -1,16 +1,54 @@
<template>
<figure>
<iframe :src="data.embed" :height="data.height" frameborder="0" allowfullscreen class="w-full"></iframe>
<figure class="embed-block">
<div class="embed-container">
<iframe
v-if="accepted"
:src="data.embed"
:height="data.height"
frameborder="0"
allowfullscreen
/>
<div
v-else
class="embed-accept"
>
<h2>Externer Inhalt</h2>
<h3>
Originaler Link: <a
:href="data.source"
target="_blank"
>{{ displayUrl }}</a>
</h3>
<p>
An dieser Stelle ist externer Inhalt eingebunden. Um den Datenschutz
zu gewährleisten, ist er derzeit ausgeblendet. Deine Privatsphäre kann
auf externen Seiten nicht sichergestellt werden. Möchtest du den
Inhalt dennoch anzeigen?
</p>
<button @click="accepted = true">
Anzeigen
</button>
</div>
</div>
<figcaption v-html="data.caption"></figcaption>
<figcaption v-html="data.caption" />
</figure>
</template>
<script>
export default {
props: ["data"],
props: ['data'],
data() {
return { accepted: false };
},
computed: {
displayUrl() {
if (!this.data) {
return '';
}
const url = new URL(this.data.source);
return url.hostname;
},
},
};
</script>
<style>
</style>

View File

@@ -1,11 +1,11 @@
<template>
<component :is="`h${data.level}`" :id="data.anchor">
<span v-html="data.text" />
</component>
</template>
<script>
export default {
props: ["data"],
render(createElement) {
return createElement("h" + this.data.level, this.data.text);
},
props: ['data'],
};
</script>
<style>
</style>

View File

@@ -1,16 +1,27 @@
<template>
<figure>
<img :src="data.file.url" />
<img v-lazyload :src="lowQualityUrl" :data-src="highQualityUrl">
<figcaption v-html="data.caption"></figcaption>
<figcaption v-html="data.caption" />
</figure>
</template>
<script>
import LazyLoadImage from '../directives/lazy-load-image';
export default {
props: ["data"],
directives: {
lazyload: LazyLoadImage,
},
props: ['data'],
inject: ['$baseFileUrl'],
computed: {
lowQualityUrl() {
return `${this.$baseFileUrl}/${this.data.file.id}?type=jpg&w=100&b=50`;
},
highQualityUrl() {
return `${this.$baseFileUrl}/${this.data.file.id}?w=1200`;
},
},
};
</script>
<style>
</style>

View File

@@ -1,24 +1,31 @@
<template>
<div class="link-block">
<a :href="data.link" class="link-block-href">
<a
:href="data.link"
target="_blank"
class="link-block-href"
>
<div class="link-block-meta">
<div class="link-block-title">{{ data.meta.title }}</div>
<p class="link-block-description">{{ data.meta.description }}</p>
<div class="link-block-anchor">{{ domain }}</div>
</div>
<div class="link-block-image" :style="{ backgroundImage: data.meta.image.url }"></div>
<div
class="link-block-image"
:style="{ backgroundImage: `url(${data.meta.image.url}` }"
/>
</a>
</div>
</template>
<script>
export default {
props: ["data"],
props: ['data'],
computed: {
domain() {
return this.data.link.replace(/(https|http):\/\//, "").split(/[/?#]/)[0];
}
}
return this.data.link.replace(/(https|http):\/\//, '').split(/[/?#]/)[0];
},
},
};
</script>

View File

@@ -1,14 +1,12 @@
<template>
<component :is="data.style === 'ordered' ? 'ol' : 'ul'">
<li v-for="item in data.items" :key="item" v-html="item"></li>
</component>
</template>
<script>
export default {
props: ["data"],
render(createElement) {
return createElement(
this.data.style === "ordered" ? "ol" : "ul",
this.data.items.map((item) => createElement("li", item))
);
},
props: ['data'],
};
</script>
<style>
</style>

View File

@@ -0,0 +1,23 @@
<template>
<figure>
<div v-html="content" />
</figure>
</template>
<script>
import katex from 'katex';
export default {
props: ['data'],
data() {
return {
content: '',
};
},
mounted() {
this.content = katex.renderToString(this.data.text, {
throwOnError: false,
});
},
};
</script>

View File

@@ -1,10 +1,10 @@
<template>
<p v-html="data.text"></p>
<p v-html="data.text" />
</template>
<script>
export default {
props: ["data"],
props: ['data'],
};
</script>

View File

@@ -1,14 +1,14 @@
<template>
<blockquote>
<p v-html="data.text"></p>
<p v-html="data.text" />
<footer v-html="data.caption"></footer>
<footer v-html="data.caption" />
</blockquote>
</template>
<script>
export default {
props: ["data"],
props: ['data'],
};
</script>

View File

@@ -1,15 +1,17 @@
<template>
<div class="warning-block">
<p class="font-semibold mb-4 text-white">{{data.title}}</p>
<p v-html="data.message" class="text-white"></p>
<b v-html="title" />
<p v-html="data.message" />
</div>
</template>
<script>
export default {
props: ["data"],
props: ['data'],
computed: {
title() {
return this.data.title ? this.data.title.replace(/<br>/g, '') : '';
},
},
};
</script>
<style>
</style>

View File

@@ -0,0 +1,38 @@
export default {
mounted: (el) => {
function loadImage() {
const image = new Image();
image.src = el.dataset.src;
image.onload = () => {
el.src = el.dataset.src;
};
}
function handleIntersect(entries, observer) {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
}
loadImage();
observer.unobserve(el);
});
}
function createObserver() {
const options = {
root: null,
threshold: "0",
};
const observer = new IntersectionObserver(handleIntersect, options);
observer.observe(el);
}
if (!window.IntersectionObserver) {
loadImage();
} else {
createObserver();
}
},
};

View File

@@ -1,4 +1,4 @@
module.exports = function(size) {
export default function (size) {
var result;
switch (true) {
@@ -27,4 +27,4 @@ module.exports = function(size) {
}
return result;
};
}

View File

@@ -1,4 +0,0 @@
import BlockContentRenderer from './components/BlockContentRenderer.vue';
export default BlockContentRenderer;
export { BlockContentRenderer };

13
src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { App } from 'vue';
import BlockContentRenderer from './components/BlockContentRenderer.vue';
export default {
install: (app: App, options: {
baseFileUrl?: string
}) => {
app.provide('$baseFileUrl', options.baseFileUrl);
app.component('BlockContentRenderer', BlockContentRenderer);
},
};
export { BlockContentRenderer };

5
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

38
tsconfig.json Normal file
View File

@@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"ESNext",
"DOM"
],
"skipLibCheck": true,
"noEmit": true,
"allowJs": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"outDir": "dist",
"declarationDir": "dist",
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
}

60
vite.config.ts Normal file
View File

@@ -0,0 +1,60 @@
import * as path from 'path'
import vue from '@vitejs/plugin-vue'
import typescript2 from 'rollup-plugin-typescript2';
import { defineConfig } from 'vite';
import dts from "vite-plugin-dts";
export default defineConfig({
plugins: [
vue(),
dts({
insertTypesEntry: true,
}),
typescript2({
check: false,
include: ["src/components/**/*.vue"],
tsconfigOverride: {
compilerOptions: {
outDir: "dist",
sourceMap: true,
declaration: true,
declarationMap: true,
},
},
exclude: ["vite.config.ts"]
})
],
build: {
cssCodeSplit: true,
lib: {
// Could also be a dictionary or array of multiple entry points
entry: "src/main.ts",
name: 'blockRenderer',
formats: ["es", "cjs", "umd"],
fileName: format => `block-renderer.${format}.js`
},
rollupOptions: {
// make sure to externalize deps that should not be bundled
// into your library
input: {
main: path.resolve(__dirname, "src/main.ts")
},
external: ['vue'],
output: {
assetFileNames: (assetInfo) => {
if (assetInfo.name === 'main.css') return 'block-renderer.css';
return assetInfo.name;
},
exports: "named",
globals: {
vue: 'Vue',
},
},
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
})