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

Lerna #33

Merged
merged 7 commits into from
Apr 4, 2023
Merged
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
77 changes: 39 additions & 38 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
basic-tests:
basic-checks:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand All @@ -21,91 +21,91 @@ jobs:
- run: npm ci
- run: npm run check-packages

build-common:
needs: basic-tests
lint:
needs: basic-checks
runs-on: ubuntu-latest
timeout-minutes: 15
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
path: node_modules/.cache/nx
key: ${{ runner.os }}-${{ hashFiles('package-lock.json') }}-build
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.node-version'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- run: npm ci
- run: npm run lint --workspace common
- run: npm run build --workspace common
- run: echo "This file is placed at the project root, so Github preserves the artifact paths (see https://github.com/actions/upload-artifact/issues/206)" > root.txt
- uses: actions/upload-artifact@v3
with:
name: dist
path: | # Important to use `|` to preserve the path (see https://github.com/actions/upload-artifact/issues/55#issuecomment-633825395)
common/dist/**
root.txt
if-no-files-found: error
- run: npm run lint

build-app:
needs: build-common
needs: basic-checks
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
workspace: [ client, server ]
workspace: [
common,
client,
server,
portal
]
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
name: dist
path: node_modules/.cache/nx
key: ${{ runner.os }}-nx-${{ hashFiles('package-lock.json') }}
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.node-version'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- run: npm ci
- run: npm run lint --workspace ${{ matrix.workspace }}
- run: npm run build --workspace ${{ matrix.workspace }}
- run: npx lerna run build --scope=@fumix/fu-blog-${{ matrix.workspace }}
- run: echo "This file is placed at the project root, so Github preserves the artifact paths (see https://github.com/actions/upload-artifact/issues/206)" > root.txt
- uses: actions/upload-artifact@v3
with:
name: dist
path: |
path: | # Important to use `|` to preserve the path (see https://github.com/actions/upload-artifact/issues/55#issuecomment-633825395)
${{ matrix.workspace }}/dist/**
root.txt
if-no-files-found: error

build-portal:
test:
needs: build-app
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
- name: Cache build
id: cache-build
uses: actions/cache@v3
with:
name: dist
path: node_modules/.cache/nx
key: ${{ runner.os }}-nx-${{ hashFiles('package-lock.json') }}
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.node-version'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
- run: npm ci
- run: npm run lint --workspace portal
- run: npm run build --workspace portal
- uses: actions/upload-artifact@v3
with:
name: dist
path: |
${{ matrix.workspace }}/dist/**
root.txt
if-no-files-found: error
- run: npm run test

publish:
if: contains('
refs/heads/main
refs/heads/develop
', github.ref)
needs: build-portal
', github.ref) && github.repository == 'fumiX/fuBlog'
needs: [test, lint]
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -145,9 +145,10 @@ jobs:

publish-db-image:
if: contains('
refs/heads/main
refs/heads/develop
', github.ref)
refs/heads/main
refs/heads/develop
', github.ref) && github.repository == 'fumiX/fuBlog'
needs: [test, lint]
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
5 changes: 2 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"build-only": "vite build",
"test": "echo \"At the moment there are no tests for the 'client' package …\"",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
"lintfix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
"lint": "prettier --check 'src/**/*.(ts|vue)' && eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
"lintfix": "prettier 'src/**/*.(ts|vue)' --write && eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.3.0",
Expand All @@ -18,7 +18,6 @@
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@fumix/fu-blog-common": "*",
"@intlify/unplugin-vue-i18n": "^0.9.2",
"bootstrap": "^5.2.3",
"buffer": "^6.0.3",
"luxon": "^3.3.0",
Expand Down
19 changes: 4 additions & 15 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ import LightDarkToggler from "@client/components/LightDarkToggler.vue";
import LoginButton from "@client/components/LoginButton.vue";
import SearchComponent from "@client/components/SearchComponent.vue";
import UserName from "@client/components/UserName.vue";
import { loadIdToken, saveIdToken } from "@client/util/storage.js";
import type { SavedOAuthToken, User, UserRolePermissionsType } from "@fumix/fu-blog-common";
import { AuthEndpoints } from "@client/util/api-client.js";
import { saveIdToken } from "@client/util/storage.js";
import type { User, UserRolePermissionsType } from "@fumix/fu-blog-common";
import { permissionsForUser } from "@fumix/fu-blog-common";
import { defineComponent, onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
Expand All @@ -84,19 +85,7 @@ export default defineComponent({
};

const getLoggedInUser = async (): Promise<User> => {
const tokenObj: SavedOAuthToken | undefined = loadIdToken();

const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...tokenObj }),
};

const postUrl = `/api/auth/loggedInUser/`;
const response = await fetch(postUrl, requestOptions);
const data = await response.json();

return data.user;
return AuthEndpoints.getLoggedInUser().then((it) => it.user);
};

const setLoginUSerAndPermissions = async () => {
Expand Down
92 changes: 92 additions & 0 deletions client/src/components/ImagePreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<template>
<div class="img-container">
<img :src="dataUrl" class="img-thumbnail" v-on:dragstart="onDragStart($event)" :draggable="showPaste" />
<div>
<strong :title="value.name">{{ value.name }}</strong>
<span>{{ convertToHumanReadableFileSize(value.size) }}</span
><br />
<button class="btn btn-primary mx-1" v-if="showPaste" @click="$emit('paste', getMarkdownString())">
<fa-icon :icon="faPaste"></fa-icon>
</button>
<button class="btn btn-outline-danger mx-1" v-if="showDelete" @click="$emit('delete')"><fa-icon :icon="faTrash"></fa-icon></button>
</div>
</div>
</template>
<script lang="ts">
import { faPaste, faTrash } from "@fortawesome/free-solid-svg-icons";
import { blobToArray, bytesToDataUrl, convertToHumanReadableFileSize, escapeMarkdownAltText } from "@fumix/fu-blog-common";
import { defineComponent, type PropType } from "vue";

export default defineComponent({
emits: ["paste", "delete"],
methods: {
convertToHumanReadableFileSize,
getMarkdownString() {
return `![${escapeMarkdownAltText(this.value.name)}](${this.hash})`;
},
onDragStart(event: DragEvent) {
if (this.showPaste) {
event.dataTransfer?.setData("text/markdown", this.getMarkdownString());
}
},
},
props: {
hash: {
type: String,
required: true,
},
value: {
type: Object as PropType<File>,
required: true,
},
showDelete: {
type: Boolean,
default: true,
},
showPaste: {
type: Boolean,
default: true,
},
},
async setup(props) {
const dataUrl = await blobToArray(props.value).then((it) => bytesToDataUrl(props.value.type, it));
return {
dataUrl,
faPaste,
faTrash,
};
},
});
</script>
<style scoped lang="scss">
div.img-container {
width: 12rem;
margin: 0.25rem;
position: relative;
display: inline-block;
text-align: center;
vertical-align: bottom;
img {
&[draggable="true"] {
cursor: move;
}
min-width: 2rem;
min-height: 2rem;
max-width: 12rem;
max-height: 12rem;
}
div {
font-size: 0.75em;
strong {
font-weight: bold;
text-overflow: ellipsis;
white-space: nowrap;
overflow: clip;
display: block;
}
display: block;
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
}
</style>
17 changes: 16 additions & 1 deletion client/src/components/MarkDown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
</style>

<script lang="ts">
import { blobToArray, bytesToDataUrl } from "@fumix/fu-blog-common";
import type { PropType } from "vue";
import { defineComponent, ref, watch } from "vue";
import { MarkdownConverterClient } from "../markdown-converter-client.js";
Expand All @@ -30,6 +31,10 @@ export default defineComponent({
markdown: {
type: String as PropType<string | null>,
},
customImageUrls: {
type: Object as PropType<{ [sha256: string]: File }>,
default: () => {},
},
},

emits: ["loading"],
Expand All @@ -40,8 +45,18 @@ export default defineComponent({
watch(props, async () => {
try {
emits.emit("loading", true);
sanitizedHtml.value = await MarkdownConverterClient.Instance.convert(props.markdown ?? "");
sanitizedHtml.value = await MarkdownConverterClient.Instance.convert(props.markdown ?? "", {
walkTokens: async (token) => {
if (token.type === "image") {
const customFile = props.customImageUrls[token.href];
if (customFile) {
token.href = await blobToArray(customFile).then((it) => bytesToDataUrl(customFile.type, it));
}
}
},
});
} catch (e) {
console.error("Markdown error", e);
// TODO erro handling
} finally {
emits.emit("loading", false);
Expand Down
2 changes: 1 addition & 1 deletion client/src/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"preview": {
"title": "Vorschau"
},
"imageupload": "Bild hochladen"
"imageupload": "keine angehängten Bilder | ein angehängtes Bild | {count} angehängte Bilder"
},
"confirm": {
"title": "Löschen bestätigen",
Expand Down
14 changes: 11 additions & 3 deletions client/src/plugins/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { createI18n } from "vue-i18n";
import de from "../i18n/de.json";
import en from "../i18n/en.json";
import { createI18n } from "vue-i18n";

const i18n = createI18n({
legacy: false,
globalInjection: true,
missingWarn: false,
fallbackWarn: false,
locale: import.meta.env.VITE_I18N_LOCALE || "de",
fallbackLocale: import.meta.env.VITE_I18N_FALLBACK_LOCALE || "en",
locale: import.meta.env.VITE_I18N_LOCALE ?? "de",
fallbackLocale: import.meta.env.VITE_I18N_FALLBACK_LOCALE ?? "en",
messages: { de, en },
});

export const t = (key: string): string => {
return key ? i18n.global.t(key) : key;
};
export const tc = (key: string, n: number): string => {
return key ? i18n.global.t(key, { n }, n) : key;
};

export default i18n;
18 changes: 9 additions & 9 deletions client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ const router = createRouter({
component: () => import("@client/views/PostsView.vue"),
},
{
path: "/posts/post/:id",
path: "/posts/post/:id([1-9][0-9]+)",
name: "post",
// route level code-splitting
// this generates a separate chunk (Articles.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("@client/views/PostView.vue"),
},
{
path: "/posts/post/form",
name: "form",
// route level code-splitting
// this generates a separate chunk (Articles.[hash].js) for this route
// which is lazy-loaded when the route is visited.
path: "/posts/post/new",
name: "new-post",
component: () => import("@client/views/PostFormView.vue"),
},
{
path: "/posts/post/:postId([1-9][0-9]*)/edit",
name: "edit-post",
component: () => import("@client/views/PostFormView.vue"),
props: true,
},
],
});
Expand Down
Loading