From 3fdced84bf9dc0edad14affb15b4230002baa474 Mon Sep 17 00:00:00 2001 From: Aran Roig Date: Sat, 2 May 2026 16:15:36 +0200 Subject: [PATCH] CI/CD test --- .gitea/workflows/deploy.yml | 41 ++++-- docker-compose.yml | 21 +++ .../app/components/viewer/content/Note.vue | 32 +++-- .../components/viewer/widgets/RollWidget.vue | 59 ++++++++ .../windows/CreateCampaignWindow.vue | 10 +- frontend/app/services/Marker.js | 53 +++++-- frontend/app/services/widgets/DiceParser.js | 130 ++++++++++++++++++ 7 files changed, 315 insertions(+), 31 deletions(-) create mode 100644 docker-compose.yml create mode 100644 frontend/app/components/viewer/widgets/RollWidget.vue create mode 100644 frontend/app/services/widgets/DiceParser.js diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index b6c9ce7..a6d84de 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -13,6 +13,23 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Install SSH client + run: | + apt-get update -y && apt-get install -y openssh-client + + - name: Setup SSH inside container + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + + # Add the container host to known_hosts + ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts + + - name: Log in to registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.aranroig.com -u "${{ secrets.REGISTRY_USER }}" --password-stdin + - name: Build frontend run: | docker build -t git.aranroig.com/${{ secrets.REGISTRY_USER }}/dragonroll-frontend:latest ./frontend @@ -23,17 +40,17 @@ jobs: docker build -t git.aranroig.com/${{ secrets.REGISTRY_USER }}/dragonroll-backend:latest ./backend docker push git.aranroig.com/${{ secrets.REGISTRY_USER }}/dragonroll-backend:latest - # - name: Copy files - # run: | - # scp docker-compose.yml deploy@${{ secrets.DEPLOY_HOST}}:/var/www/app/ - # scp nginx.conf deploy@${{ secrets.DEPLOY_HOST }}:/var/www/app/nginx.conf + - name: Copy files + run: | + scp docker-compose.yml deploy@${{ secrets.DEPLOY_HOST}}:/var/www/app/ + scp nginx.conf deploy@${{ secrets.DEPLOY_HOST }}:/var/www/app/nginx.conf - #- name: Deploy - # run: | - # ssh deploy@${{ secrets.DEPLOY_HOST }} << 'EOF' - # echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.aranroig.com -u "${{ secrets.REGISTRY_USER }}" --password-stdin - # cd /var/www/app/ - # docker-compose pull - # docker-compose up -d - # EOF + - name: Deploy + run: | + ssh deploy@${{ secrets.DEPLOY_HOST }} << 'EOF' + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login git.aranroig.com -u "${{ secrets.REGISTRY_USER }}" --password-stdin + cd /var/www/app/ + docker-compose pull + docker-compose up -d + EOF diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a76cc8c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3.9" + +services: + nginx: + image: nginx:latest + ports: + - "3000:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - frontend + - backend + restart: always + + frontend: + image: git.aranroig.com/syndria98/dragonroll-frontend:latest + restart: always + + backend: + image: git.aranroig.com/syndria98/dragonroll-backend:latest + restart: always \ No newline at end of file diff --git a/frontend/app/components/viewer/content/Note.vue b/frontend/app/components/viewer/content/Note.vue index e681e7b..4340942 100644 --- a/frontend/app/components/viewer/content/Note.vue +++ b/frontend/app/components/viewer/content/Note.vue @@ -2,7 +2,7 @@ import { onMounted, onUnmounted, ref, createApp } from 'vue'; import ToastManager from '~/components/managers/ToastManager.vue'; import { emitter } from '~/services/Emitter'; -import { ParseMarkdown } from '~/services/Marker'; +import { GetWidget, ParseMarkdown } from '~/services/Marker'; import Server from '~/services/Server'; import { DisplayToast } from '~/services/Toaster'; import TestWidget from '../widgets/TestWidget.vue'; @@ -15,6 +15,9 @@ const sourceText = ref(''); // Original markdown source, used for editing const displayText = ref(''); // Compiled HTML from markdown const editingMode = ref(false); +const editableTitle = ref(null); +const title = ref(props.title); +const displayTitle = ref(''); function closeNote(){ emitter.emit('delete-note', props.noteKey); @@ -28,12 +31,9 @@ function mountComponents() { const nodes = document.querySelectorAll('.vue-component'); nodes.forEach(el => { - const app = createApp(TestWidget, { content: el.dataset.content }); + const app = createApp(GetWidget(el.dataset.component), { content: el.dataset.content }); app.mount(el); - console.log("Mounted a component") }); - - console.log("Huh") } /// @@ -51,6 +51,8 @@ watch(sourceText, (newText) => { onMounted(() => { sourceText.value = props.text; + title.value = props.title; + displayTitle.value = props.title; // window.addEventListener('keydown', handleKeydown); setTimeout(() => setupCallout(), 0); update(); @@ -65,7 +67,10 @@ function handleKeydown(e) { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'e') { e.preventDefault(); // prevent browser default behavior editingMode.value = !editingMode.value; - if(!editingMode.value) update(); + if(!editingMode.value){ + update(); + SaveNote(); // Save when switching to display mode + } return; } @@ -80,13 +85,14 @@ function handleKeydown(e) { function SaveNote(){ Server().post('/note/update', { id: props.noteKey, - content: sourceText.value + content: sourceText.value, + title: title.value, }).then((response) => { if(response.data.status !== 'ok'){ // Handle error (e.g., show a notification) return; } - DisplayToast('green', "Note saved successfully.", 500); + // DisplayToast('green', "Note saved successfully.", 500); }).catch((error) => { // Handle error (e.g., show a notification) }); @@ -138,6 +144,10 @@ function setupCallout() { } +const editTitle = (e) => { + title.value = e.target.innerText; +} + diff --git a/frontend/app/components/viewer/widgets/RollWidget.vue b/frontend/app/components/viewer/widgets/RollWidget.vue new file mode 100644 index 0000000..649f7c8 --- /dev/null +++ b/frontend/app/components/viewer/widgets/RollWidget.vue @@ -0,0 +1,59 @@ + + + + + \ No newline at end of file diff --git a/frontend/app/components/windows/CreateCampaignWindow.vue b/frontend/app/components/windows/CreateCampaignWindow.vue index 8072212..f748b6d 100644 --- a/frontend/app/components/windows/CreateCampaignWindow.vue +++ b/frontend/app/components/windows/CreateCampaignWindow.vue @@ -12,6 +12,7 @@ const wrapper = ref(null); const props = defineProps(['data']); const data = props.data; +const loading = ref(false); let id = data.id; @@ -29,11 +30,13 @@ const colorPicker = ref(null); function NewCampaign(){ const color = colorPicker.value.GetColor(); console.log(color); + loading.value = true; Server().post('/campaign/create', { name: campaignName.value, description: campaignDescription.value, color: colorPicker.value.GetColor(), }).then((response) => { + loading.value = false; console.log(response.data); DisplayToast('green', $t('campaigns.create.success'), 3000); ClearWindow({id}); @@ -63,7 +66,12 @@ function NewCampaign(){
diff --git a/frontend/app/services/Marker.js b/frontend/app/services/Marker.js index 6bfea80..7d87c6a 100644 --- a/frontend/app/services/Marker.js +++ b/frontend/app/services/Marker.js @@ -1,15 +1,25 @@ import { Marked } from "marked"; - -const marker = new Marked(); - -// optional: use a shared marked instance inside renderer -const renderMarkdown = (text) => marker.parse(text); - -const WidgetMap = { +const widget_map = { test: () => import("~/components/viewer/widgets/TestWidget.vue"), table: () => import("~/components/viewer/widgets/TableWidget.vue"), + roll: () => import("~/components/viewer/widgets/RollWidget.vue"), }; +const componentCache = {} + +const GetWidget = (type) => { + if (!componentCache[type]) { + componentCache[type] = defineAsyncComponent( + widget_map[type] + ) + } + + return componentCache[type] +} + + +const marker = new Marked(); + const extension = { name: "something", level: "block", @@ -33,12 +43,37 @@ const extension = { }, }; +const inlineExtension = { + name: "something_inline", + level: "inline", + start(src) { + return src.indexOf("@"); + }, + + tokenizer(src) { + const rule = /^@(\w+)\s*\[([^\]]*)\]/; + const match = rule.exec(src); + if (!match) return; + + return { + type: "something_inline", + raw: match[0], + name: match[1], + text: match[2], + }; + }, + + renderer(token) { + return ``; + }, +}; + marker.use({ - extensions: [extension], + extensions: [extension, inlineExtension], }); function ParseMarkdown(source) { return marker.parse(source || ""); } -export { ParseMarkdown, WidgetMap }; \ No newline at end of file +export { ParseMarkdown, GetWidget }; \ No newline at end of file diff --git a/frontend/app/services/widgets/DiceParser.js b/frontend/app/services/widgets/DiceParser.js new file mode 100644 index 0000000..6022fd0 --- /dev/null +++ b/frontend/app/services/widgets/DiceParser.js @@ -0,0 +1,130 @@ +// dice-parser.js + +function roll(sides) { + return Math.floor(Math.random() * sides) + 1; +} + +function tokenize(expr) { + const re = /(\d*d\d+(?:adv|dis|kh\d+|kl\d+)?|\d+|[+\-*\/()])/gi; + const tokens = []; + let m; + while ((m = re.exec(expr)) !== null) tokens.push(m[0].toLowerCase()); + return tokens; +} + +function parseDiceToken(tok) { + const m = tok.match(/^(\d*)d(\d+)(adv|dis|kh(\d+)|kl(\d+))?$/i); + if (!m) return null; + const count = parseInt(m[1] || '1'); + const sides = parseInt(m[2]); + const mod = (m[3] || '').toLowerCase(); + if (count < 1 || count > 1000 || sides < 2 || sides > 10000) + throw new Error(`Invalid dice: ${tok}`); + return { count, sides, mod }; +} + +export function parse(expr) { + const tokens = tokenize(expr); + if (!tokens.length) throw new Error('Empty expression'); + let pos = 0; + const rolls = []; + + function peek() { return tokens[pos]; } + function consume() { return tokens[pos++]; } + + function parseExpr() { return parseAddSub(); } + + function parseAddSub() { + let left = parseMulDiv(); + while (peek() === '+' || peek() === '-') { + const op = consume(); + const right = parseMulDiv(); + left = { + value: op === '+' ? left.value + right.value : left.value - right.value, + rolls: [...left.rolls, ...right.rolls], + }; + } + return left; + } + + function parseMulDiv() { + let left = parseUnary(); + while (peek() === '*' || peek() === '/') { + const op = consume(); + const right = parseUnary(); + if (op === '/' && right.value === 0) throw new Error('Division by zero'); + left = { + value: op === '*' ? left.value * right.value : Math.floor(left.value / right.value), + rolls: [...left.rolls, ...right.rolls], + }; + } + return left; + } + + function parseUnary() { + if (peek() === '-') { + consume(); + const r = parsePrimary(); + return { value: -r.value, rolls: r.rolls }; + } + return parsePrimary(); + } + + function parsePrimary() { + const tok = peek(); + if (!tok) throw new Error('Unexpected end of expression'); + + if (tok === '(') { + consume(); + const inner = parseExpr(); + if (peek() !== ')') throw new Error('Missing closing )'); + consume(); + return inner; + } + + const diceInfo = parseDiceToken(tok); + if (diceInfo) { + consume(); + return rollDice(diceInfo); + } + + if (/^\d+$/.test(tok)) { + consume(); + return { value: parseInt(tok), rolls: [] }; + } + + throw new Error(`Unexpected token: ${tok}`); + } + + function rollDice({ count, sides, mod }) { + let rawRolls, kept; + + if (mod === 'adv') { + rawRolls = [roll(sides), roll(sides)]; + kept = [Math.max(...rawRolls)]; + } else if (mod === 'dis') { + rawRolls = [roll(sides), roll(sides)]; + kept = [Math.min(...rawRolls)]; + } else if (mod.startsWith('kh')) { + const k = parseInt(mod.slice(2)); + rawRolls = Array.from({ length: count }, () => roll(sides)); + kept = [...rawRolls].sort((a, b) => b - a).slice(0, k); + } else if (mod.startsWith('kl')) { + const k = parseInt(mod.slice(2)); + rawRolls = Array.from({ length: count }, () => roll(sides)); + kept = [...rawRolls].sort((a, b) => a - b).slice(0, k); + } else { + rawRolls = Array.from({ length: count }, () => roll(sides)); + kept = rawRolls.slice(); + } + + const entry = { sides, rawRolls, kept, mod, value: kept.reduce((a, b) => a + b, 0) }; + rolls.push(entry); + return { value: entry.value, rolls: [entry] }; + } + + const result = parseExpr(); + if (pos < tokens.length) throw new Error(`Unexpected token: ${tokens[pos]}`); + + return { total: result.value, rolls: result.rolls }; +} \ No newline at end of file