Link support 1/2
All checks were successful
Build and Deploy Nuxt / build (push) Successful in 59s

This commit is contained in:
2026-05-03 01:02:13 +02:00
parent 030060286f
commit 94e2b8bd47
6 changed files with 71 additions and 169 deletions

View File

@@ -4,7 +4,6 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { useCampaignService } from '~/services/Campaign.js';
import { emitter } from '~/services/Emitter';
import Server from '~/services/Server';
import { CreateWindow } from '~/services/Windows';
const { Campaign } = useCampaignService();
const notes = ref([]);
@@ -64,14 +63,31 @@ function toggleSidebar() {
sidebarCollapsed.value = !sidebarCollapsed.value;
}
function openCreateNoteWindow() {
async function createNote() {
if (!Campaign.value) {
return;
}
CreateWindow('create_note');
const campaignId = Campaign.value?._id
try {
const response = await Server().post('/note/create', {
title: 'New note',
content: "",
campaign: campaignId
});
if (response.data.status !== 'ok') {
return;
}
emitter.emit('note-created', response.data.note);
} catch (err) {
console.log(err);
}
}
function openNote(note) {
emitter.emit('push-note', note);
}
@@ -136,7 +152,7 @@ watch(Campaign, () => {
<button
class="sidebar-action"
type="button"
@click="openCreateNoteWindow"
@click="createNote"
:disabled="!Campaign"
title="New note"
aria-label="New note"

View File

@@ -10,12 +10,30 @@ const campaignName = computed(() => {
return Campaign.value?.name ?? 'Campaign';
});
function openCreateNoteWindow() {
async function createNote() {
if (!Campaign.value) {
return;
}
CreateWindow('create_note');
const campaignId = Campaign.value?._id
try {
const response = await Server().post('/note/create', {
title: 'New note',
content: content.value,
campaign: campaignId
});
if (response.data.status !== 'ok') {
return;
}
emitter.emit('note-created', response.data.note);
} catch (err) {
error.value = 'Unable to create note.';
} finally {
isSaving.value = false;
}
}
function exitToMainMenu() {
@@ -42,7 +60,7 @@ function exitToMainMenu() {
<img class="top-bar-button-icon" src="/icons/iconoir/regular/nav-arrow-left.svg" alt="" aria-hidden="true">
<span>Main Menu</span>
</button>
<button class="note-button sound-click" type="button" @click="openCreateNoteWindow" :disabled="!Campaign">
<button class="note-button sound-click" type="button" @click="createNote" :disabled="!Campaign">
<img class="note-button-icon" src="/icons/iconoir/regular/plus.svg" alt="" aria-hidden="true">
<span>New Note</span>
</button>

View File

@@ -171,7 +171,7 @@ onMounted(() => {
.roll-btn {
padding: 8px;
margin-right: 4px;
margin-right: 8px;
}
</style>

View File

@@ -1,150 +0,0 @@
<script setup>
import { computed, onMounted, ref } from 'vue';
import { SetupHandle, SetSize, ResetPosition, Top, ClearWindow } from '@/services/Windows';
import WindowHandle from './partials/WindowHandle.vue';
import Server from '~/services/Server';
import { emitter } from '~/services/Emitter';
import { useCampaignService } from '~/services/Campaign';
const handle = ref(null);
const wrapper = ref(null);
const props = defineProps(['data']);
const data = props.data;
const { Campaign } = useCampaignService();
const id = data.id;
const title = ref('');
const content = ref('');
const isSaving = ref(false);
const error = ref('');
const campaignId = computed(() => {
return Campaign.value?._id ?? Campaign.value?.id ?? null;
});
onMounted(() => {
Top(wrapper);
SetupHandle(id, handle);
SetSize(id, { width: 640, height: 520 });
ResetPosition(id, "center");
});
async function createNote() {
if (!campaignId.value || isSaving.value) {
return;
}
isSaving.value = true;
error.value = '';
try {
const response = await Server().post('/note/create', {
title: title.value.trim() || 'Untitled Note',
content: content.value,
campaign: campaignId.value
});
if (response.data.status !== 'ok') {
error.value = response.data.msg ?? 'Unable to create note.';
return;
}
emitter.emit('note-created', response.data.note);
ClearWindow({ id });
} catch (err) {
error.value = 'Unable to create note.';
} finally {
isSaving.value = false;
}
}
</script>
<template>
<div class="window-wrapper" :id="'window-wrapper-' + id" ref="wrapper">
<WindowHandle :window="id" ref="handle"></WindowHandle>
<div class="body">
<form @submit.prevent="createNote">
<div class="form-field">
<label>Title</label>
<input v-model="title" type="text" name="noteTitle" placeholder="Enter a note title" autocomplete="off">
</div>
<div class="form-field stacked">
<label>Content</label>
<textarea v-model="content" name="noteContent" placeholder="Write your note here"></textarea>
</div>
<div v-if="error" class="form-error">
{{ error }}
</div>
<div class="form-actions">
<button class="btn-primary sound-click" :disabled="isSaving">
{{ isSaving ? 'Creating...' : 'Create Note' }}
</button>
</div>
</form>
</div>
</div>
</template>
<style scoped>
.window-wrapper {
display: flex;
align-items: center;
flex-direction: column;
}
.body {
width: 100%;
}
form {
margin: 10px;
display: flex;
flex-direction: column;
gap: 12px;
}
.form-field {
display: flex;
align-items: center;
gap: 12px;
}
.form-field > * {
flex: 1;
}
.stacked {
align-items: stretch;
flex-direction: column;
gap: 6px;
}
label {
text-align: left;
}
textarea {
min-height: 300px;
resize: vertical;
}
.form-error {
color: #9e2a2b;
font-size: 14px;
}
.form-actions {
display: flex;
justify-content: center;
}
.form-actions button {
width: 100%;
}
</style>

View File

@@ -27,7 +27,7 @@ const GetWidget = (type, name) => {
const marker = new Marked();
const extension = {
name: "something",
name: "widget",
level: "block",
tokenizer(src) {
@@ -37,7 +37,7 @@ const extension = {
if (!match) return;
return {
type: "something",
type: "widget",
raw: match[0],
name: match[1],
text: match[2],
@@ -50,7 +50,7 @@ const extension = {
};
const inlineExtension = {
name: "something_inline",
name: "widget_inline",
level: "inline",
start(src) {
return src.indexOf("@");
@@ -62,7 +62,7 @@ const inlineExtension = {
if (!match) return;
return {
type: "something_inline",
type: "widget_inline",
raw: match[0],
name: match[1],
text: match[2],
@@ -74,8 +74,32 @@ const inlineExtension = {
},
};
const linkExtension = {
name: "link_to",
level: "inline",
start(src) {
return src.indexOf("[[");
},
tokenizer(src) {
const rule = /^\[\[([^\n]*)\]\]/;
const match = rule.exec(src);
if (!match) return;
return {
type: "link_to",
raw: match[0],
link: match[1],
};
},
renderer(token) {
return `<span class="vue-link" data-href="${token.link}"></span>`;
},
};
marker.use({
extensions: [extension, inlineExtension],
extensions: [extension, inlineExtension, linkExtension],
});
function ParseMarkdown(source) {

View File

@@ -34,12 +34,6 @@ const defWindows = {
component: () => import('~/components/windows/CreateCampaignWindow.vue'),
close: () => ClearWindow({type: 'create_campaign'}),
movable: true
},
create_note: {
title: "Create note",
component: () => import('~/components/windows/CreateNoteWindow.vue'),
close: () => ClearWindow({type: 'create_note'}),
movable: true
}
}