Second commit

This commit is contained in:
2026-04-20 20:30:42 +02:00
parent 9f2578f7d2
commit 254a645c25
1072 changed files with 4491 additions and 17 deletions

View File

@@ -1,6 +1,24 @@
<script setup lang="ts">
import Content from './components/viewer/content/Content.vue';
import StatusBar from './components/viewer/statusbar/StatusBar.vue';
import TopBar from './components/viewer/TopBar.vue';
</script>
<template>
<div>
<NuxtRouteAnnouncer />
<NuxtWelcome />
<div class="viewer">
<TopBar></TopBar>
<Content></Content>
<StatusBar></StatusBar>
</div>
</template>
<style scoped>
.viewer {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
}
</style>

View File

View File

View File

@@ -0,0 +1,127 @@
@font-face {
font-family: "BookInsanityRemake";
src:url('/fonts/5e/Bookinsanity.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "BookInsanityRemake";
src:url('/fonts/5e/Bookinsanity Bold.woff2');
font-weight:bold;
font-style:normal
}
@font-face {
font-family: "BookInsanityRemake";
src:url('/fonts/5e/Bookinsanity Italic.woff2');
font-weight:normal;
font-style:italic
}
@font-face {
font-family: "BookInsanityRemake";
src:url('/fonts/5e/Bookinsanity Bold Italic.woff2');
font-weight:bold;
font-style:italic
}
@font-face {
font-family: "ScalySansRemake";
src:url('/fonts/5e/Scaly Sans.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "ScalySansRemake";
src:url('/fonts/5e/Scaly Sans Bold.woff2');
font-weight:bold;
font-style:normal
}
@font-face {
font-family: "ScalySansRemake";
src:url('/fonts/5e/Scaly Sans Italic.woff2');
font-weight:normal;
font-style:italic
}
@font-face {
font-family: "ScalySansRemake";
src:url('/fonts/5e/Scaly Sans Bold Italic.woff2');
font-weight:bold;
font-style:italic
}
@font-face {
font-family: "ScalySansSmallCapsRemake";
src:url('/fonts/5e/Scaly Sans Caps.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "WalterTurncoat";
src:url('/fonts/5e/WalterTurncoat-Regular.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "MrEavesRemake";
src:url('/fonts/5e/Mr Eaves Small Caps.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "SolberaImitationRemake";
src:url('/fonts/5e/Solbera Imitation Tweak.woff2');
font-weight:100 1000;
font-style:normal;
font-style:italic
}
@font-face {
font-family: "NodestoCapsCondensed";
src:url('/fonts/5e/Nodesto Caps Condensed.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "NodestoCapsCondensed";
src:url('/fonts/5e/Nodesto Caps Condensed Bold.woff2');
font-weight:bold;
font-style:normal
}
@font-face {
font-family: "NodestoCapsCondensed";
src:url('/fonts/5e/Nodesto Caps Condensed Italic.woff2');
font-weight:normal;
font-style:italic
}
@font-face {
font-family: "NodestoCapsCondensed";
src:url('/fonts/5e/Nodesto Caps Condensed Bold Italic.woff2');
font-weight:bold;
font-style:italic
}
@font-face {
font-family: "NodestoCapsWide";
src:url('/fonts/5e/Nodesto Caps Wide.woff2');
font-weight:normal;
font-style:normal
}
@font-face {
font-family: "Overpass";
src:url('/fonts/5e/Overpass Medium.woff2');
font-weight:500;
font-style:normal
}
@font-face {
font-family: "Davek";
src:url('/fonts/5e/Davek.woff2');
font-weight:500;
font-style:normal
}
@font-face {
font-family: "Iokharic";
src:url('/fonts/5e/Iokharic.woff2');
font-weight:500;
font-style:normal
}
@font-face {
font-family: "Rellanic";
src:url('/fonts/5e/Rellanic.woff2');
font-weight:500;
font-style:normal
}

View File

@@ -0,0 +1,40 @@
body {
color: var(--text-color);
font-family: "BookInsanityRemake", Arial, Helvetica, sans-serif;
}
body {
background-color: var(--background-color);
margin: 0;
}
* {
color: var(--text-color);
}
a {
color: var(--link-color);
}
.icon {
height: 12px;
}
*::-webkit-scrollbar
{
width: 6px;
height: 6px;
background-color: var(--color-background);
border-radius: 10px;
}
*::-webkit-scrollbar-thumb
{
background-color: var(--color-scrollbar);
border-radius: 10px;
}
.error-link {
color: var(--error-link);
}

View File

@@ -0,0 +1,41 @@
<script setup>
import TopSearchBar from './topbar/TopSearchBar.vue';
</script>
<template>
<div class="top-bar">
<div class="left">
<span class="top-bar-title"></span>
</div>
<div class="center">
<TopSearchBar></TopSearchBar>
</div>
<div class="right"></div>
</div>
</template>
<style scoped>
.top-bar {
flex-grow: 0;
flex-shrink: 0;
min-height: 40px;
width: 100%;
background-color: var(--top-bar-background-color);
display: flex;
}
.left, .right {
flex: 1;
}
.right {
text-align: right;
}
.top-bar-title {
padding: 10px;
display: flex;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,26 @@
<script setup>
import NoteContainer from './NoteContainer.vue';
const emitter = useEmitter();
function hideSearch(){
emitter.emit("hide-search-container");
}
</script>
<template>
<div class="content" v-on:click="hideSearch">
<NoteContainer></NoteContainer>
<!-- PowerMod -->
</div>
</template>
<style scoped>
.content {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -0,0 +1,171 @@
<script setup>
import { onMounted, ref } from 'vue';
// import { GetNote, GetContent } from '@/services/Content';
const props = defineProps(['text', 'title', 'noteKey']);
const noteContent = ref(null);
const emitter = useEmitter();
function gotoNote(){
// emitter.emit('goto-note', props.noteKey);
}
function closeNote(){
// emitter.emit('delete-note', props.noteKey);
}
/*
onMounted(() => {
let content = GetContent();
let elements = noteContent.value.getElementsByTagName('a');
for(let i = 0, len = elements.length; i < len; i++) {
let link = elements[i].pathname.split('/').slice(1).join('');
link = decodeURIComponent(link);
if(content[link] !== undefined){
elements[i].onclick = function (event) {
event.preventDefault();
GetNote(link, (result) => {
emitter.emit("push-note", {key: link, text: "<h1>" + result.title + "</h1>" + result.html, title: result.title});
});
return false;
}
} else {
elements[i].classList.add("error-link");
elements[i].onclick = function (event) {
event.preventDefault();
return false;
}
}
}
setTimeout(() => setupCallout(), 0);
});
function closeNote(){
emitter.emit('delete-note', props.noteKey);
}
function gotoNote(){
// emitter.emit('goto-note', props.noteKey);
}
function toggleCallout() {
const outerBlock = this.parentElement;
outerBlock.classList.toggle("is-collapsed")
const collapsed = outerBlock.classList.contains("is-collapsed")
const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
outerBlock.style.maxHeight = height + "px"
// walk and adjust height of all parents
let current = outerBlock
let parent = outerBlock.parentElement
while (parent) {
if (!parent.classList.contains("callout")) {
return
}
const collapsed = parent.classList.contains("is-collapsed")
const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight
parent.style.maxHeight = height + "px"
current = parent
parent = parent.parentElement
}
}
function setupCallout() {
const collapsible = noteContent.value.getElementsByClassName(
`callout is-collapsible`,
);
for (const div of collapsible) {
const title = div.firstElementChild;
if (title) {
title.addEventListener("click", toggleCallout)
const collapsed = div.classList.contains("is-collapsed")
const height = collapsed ? title.scrollHeight : div.scrollHeight
div.style.maxHeight = height + "px"
}
}
}
*/
</script>
<template>
<div class="note">
<div class="note-stunt" v-on:click="gotoNote">
<div class="close-button" v-on:click="closeNote">
<img class="icon" src="/icons/Pixelarticons/white/close.svg" alt="My Happy SVG"/>
</div>
<span>{{ title }}</span>
</div>
<div class="note-content-container">
<div class="note-content" ref="noteContent" v-html="text"></div>
</div>
</div>
</template>
<style scoped>
.note-stunt {
writing-mode: vertical-lr;
position: sticky;
top: 0px;
left: 0px;
bottom: 0px;
padding: 10px;
display: flex;
user-select: none;
}
.close-button {
height: 20px;
width: 20px;
margin-bottom: 5px;
display: flex;
justify-content: center;
cursor: pointer;
}
.note {
min-width: 700px;
max-width: 700px;
overflow-y: auto;
border-color: var(--note-border-color);
border-width: 0px;
border-right-width: 1px;
border-style: solid;
display: flex;
background-color: var(--background-color);
position: sticky;
top: 0px;
}
/* Contingut de cada nota */
.note-content {
padding-bottom: 60px;
overflow-y: auto;
}
.note-content-container {
margin: 20px;
}
</style>
<style>
.note-content > h1 {
text-align: center;
}
.note-content .katex-display {
max-width: 600px;
}
</style>

View File

@@ -0,0 +1,68 @@
<script setup>
import { ref, onMounted } from 'vue';
import Note from './Note.vue';
const emitter = useEmitter();
let noteData = ref([]);
const noteContainer = ref(null);
function calculateContainerWidth(){
let dom = noteContainer.value;
dom.style.width = noteData.value.length * 701 + "px";
setTimeout(() => {
for(let i = 0; i < dom.children.length; i++){
let child = dom.children[i];
child.style.left = (40 * i) + "px";
child.style.right = (40 * (dom.children.length - i) - 700) + "px";
child.style.zIndex = i + 1;
}
}, 0);
}
function pushNote(note){
noteData.value.push(note);
calculateContainerWidth();
}
emitter.on("push-note", (note) => {
pushNote(note);
})
emitter.on("delete-note", (key) => {
noteData.value = noteData.value.filter((note) => {
return note.key !== key;
});
calculateContainerWidth();
});
</script>
<template>
<div class="note-scrolling-container" id="note-scrolling-container">
<div class="note-container" ref="noteContainer" >
<Note v-for="element in noteData" :key="element.key" :text="element.text" :title="element.title" :noteKey="element.key"></Note>
</div>
</div>
</template>
<style scoped>
.note-scrolling-container {
overflow-x: auto;
width: 100%;
height: 100%;
overflow-y: none;
}
.note-container {
display: flex;
height: 100%;
margin: 0;
height: 100%;
}
</style>
<style>
</style>

View File

@@ -0,0 +1,38 @@
<script setup>
import { ref,onMounted } from 'vue';
const emitter = useEmitter();
const statusIcon = ref(null);
const statusMessage = ref(null);
emitter.on("status-bar-update", (state) => {
var preloadImage = new Image();
preloadImage.src = '/icons/Pixelarticons/black/' + state.icon + ".svg";
preloadImage.onload = function() {
statusIcon.value.src = this.src;
};
statusMessage.value.innerText = state.text;
})
onMounted(() => {
emitter.emit("status-bar-update", {text: "Hola", icon: "check"})
});
</script>
<template>
<span class="status-text">
<img class="icon" ref="statusIcon" src=""/>
<span class="status-message" ref="statusMessage"></span>
</span>
</template>
<style scoped>
.status-text {
margin-left: 10px;
flex-direction: column;
justify-content: center;
}
.status-message {
margin-left: 5px;
}
</style>

View File

@@ -0,0 +1,46 @@
<script setup>
import FetchStatus from './FetchStatus.vue';
</script>
<template>
<div class="status-bar">
<div class="left">
<FetchStatus></FetchStatus>
</div>
<div class="center">
</div>
<div class="right">
</div>
</div>
</template>
<style scoped>
.status-bar {
flex-grow: 0;
flex-shrink: 0;
min-height: 24px;
max-height: 24px;
width: 100%;
background-color: var(--top-bar-background-color);
display: flex;
font-size: 14px;
}
.left, .right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.right {
text-align: right;
}
.top-bar-title {
padding: 10px;
display: flex;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,110 @@
<script setup>
import SearchIcon from 'pixelarticons/svg/search.svg'
/*
import { ref, onMounted } from 'vue';
import { GetArrayContent, GetContent } from '@/services/Content';
import useEmitter from '@/services/Emitter';
const emitter = useEmitter();
import NoteSearchElement from "@/components/topbar/NoteSearchElement.vue";
let noteLinks = ref([]);
let searchContainer = ref(null);
emitter.on("hide-search-container", () => {
searchContainer.value.style.display = "none";
})
function updateList(event){
let content = GetArrayContent();
let query = "";
if(event !== undefined) query = event.target.value;
let filter = query.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().trim();
noteLinks.value = content.filter((noteInfo) => {
return noteInfo["title"].normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().indexOf(filter) > -1;
});
}
function searchFocus(event){
if(GetContent() === undefined) return;
searchContainer.value.style.display = "";
updateList(undefined);
}
onMounted(() => {
});
*/
</script>
<template>
<div class="top-search-container">
<div class="top-search-box">
<img class="icon search-icon" :src="SearchIcon" alt="My Happy SVG"/>
<!-- <input type="text" v-on:input="updateList" v-on:focus="searchFocus" class="search-prompt" placeholder="Buscar...">-->
<input type="text" class="search-prompt" placeholder="Buscar...">
</div>
<!-- <div class="search-container" ref="searchContainer" v-on:focusout="searchFocusout" style="display: none;">-->
<div class="search-container" style="display: none;">
<div class="search-container-list">
<!--
<NoteSearchElement v-for="element in noteLinks" :key="element.key" :title="element.title" :link="element.key"></NoteSearchElement>
-->
</div>
</div>
</div>
</template>
<style scoped>
.top-search-container {
height: 100%;
align-items: center;
display: flex;
}
.top-search-box {
align-items: center;
display: flex;
background-color: var(--search-background);
padding: 2px;
border-radius: 5px;
}
.search-prompt {
border: none;
padding: 5px;
width: 30vw;
max-width: 400px;
background: none;
margin-left: -5px;
}
.search-prompt:focus {
outline: none;
}
.search-icon {
padding: 5px;
}
.search-container {
position: absolute;
background-color: var(--search-background-container);
top: 45px;
margin-left: auto;
margin-right: auto;
max-width: 800px;
max-height: 500px;
overflow-y: scroll;
left: 0;
right: 0;
backdrop-filter: blur(15px);
border-radius: 10px;
min-width: 400px;
min-height: 500px;
z-index: 99999;
}
</style>

View File

@@ -0,0 +1,4 @@
export const useEmitter = () => {
const { $emitter } = useNuxtApp()
return $emitter
}

View File

@@ -0,0 +1,11 @@
import mitt from 'mitt'
export default defineNuxtPlugin(() => {
const emitter = mitt()
return {
provide: {
emitter
}
}
})