Skip to content

Commit

Permalink
feat: support for multiple chronos docs
Browse files Browse the repository at this point in the history
  • Loading branch information
mirkobrombin committed Jan 21, 2024
1 parent 5fb1df1 commit 3759613
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 91 deletions.
39 changes: 26 additions & 13 deletions public/chronos.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
{
"title": "Chronos Documentation",
"logoTitle": "Documentation",
"logoUrl": "/logo.svg",
"description": "A frontend in Vue.js for the Chronos documentation server.",
"baseUrl": "http://localhost:5173",
"chronosApiUrl": "http://localhost:8080/remote1",
"extraLinks": [
{
"url": "https://github.com/vanilla-os/Chronos",
"name": "Source Code"
}
]
}
"title": "Chronos Documentation",
"logoTitle": "Documentation",
"logoUrl": "/logo.svg",
"description": "A frontend in Vue.js for the Chronos documentation server.",
"baseUrl": "http://localhost:5173",
"chronosCollections": [
{
"shortName": "docs",
"title": "Docs",
"url": "http://localhost:8080/documentation",
"description": "Read the tecnical documentation for the Vanilla OS components and utilities."
},
{
"shortName": "handbook",
"title": "Handbook",
"url": "http://localhost:8080/handbook",
"description": "Are you a new Vanilla OS user? Read the handbook to learn how to use the OS."
}
],
"extraLinks": [
{
"url": "https://github.com/vanilla-os/Chronos",
"name": "Source Code"
}
]
}
51 changes: 38 additions & 13 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
<a class="navbar-item" :href="link.url" target="_blank" v-for="(link) in chronosConfig.extraLinks">
{{ link.name }}
</a>
<button class="button is-white" @click="toggleSearch()">
<button class="button is-white" @click="toggleSearch()" v-if="collectionShortName">
<span class="icon is-left">
<i class="mdi material-icons">search</i>
</span>
</button>
<div class="dropdown" :class="{ 'is-active': showLangs }">
<div class="dropdown" :class="{ 'is-active': showLangs }" v-if="collectionShortName">
<div class="dropdown-trigger">
<button class="button is-white" @click="showLangs = !showLangs">
<span>{{ chronosStore.prefLang }}</span>
Expand Down Expand Up @@ -53,11 +53,17 @@
</p>
</div>
<div class="panel-scrollable">
<a class="panel-block" @click="goToArticle(result.Slug)" v-for="(result) in searchResponse" :key="result.Slug">
<span class="panel-icon">
<i class="mdi material-icons">book</i>
</span>
{{ result.Title }}
<a class="panel-block flex-list" @click="goToArticle(result.Slug)" v-for="(result) in searchResponse"
:key="result.Slug">
<div class="panel-block-header panel-block-header--has-icon">
<span class="panel-icon">
<i class="mdi material-icons">book</i>
</span>
{{ result.Title }}
</div>
<div class="panel-block-content">
<p class="is-size-7 has-text-grey">{{ result.Description }}</p>
</div>
</a>
</div>
</nav>
Expand Down Expand Up @@ -95,13 +101,19 @@ export default defineComponent({
searchResponse: [] as Article[],
search: "",
chronosConfig: {} as ChronosConfig,
collectionShortName: "",
};
},
computed: {
chronosStore() {
return useChronosStore();
},
},
watch: {
$route(to, from) {
this.fetchLanguages();
},
},
async mounted() {
try {
// @ts-ignore
Expand All @@ -110,10 +122,9 @@ export default defineComponent({
console.error("Error fetching Chronos config:", error);
}
// @ts-ignore
this.$chronosAPI.getLangs().then((response) => {
this.langs = response;
});
// Fetch languages initially
this.fetchLanguages();
this.chronosStore.$patch((state) => {
state.prefLang = "en";
});
Expand All @@ -127,12 +138,12 @@ export default defineComponent({
},
async searchArticles() {
// @ts-ignore
this.$chronosAPI.searchArticles(this.chronosStore.prefLang, this.search).then((response) => {
this.$chronosAPI.searchArticles(this.chronosStore.prefLang, this.search, this.collectionShortName).then((response) => {
this.searchResponse = response;
});
},
goToArticle(slug: string) {
this.$router.push(`/${this.chronosStore.prefLang}/${slug}`);
this.$router.push(`/${this.collectionShortName}/${this.chronosStore.prefLang}/${slug}`);
this.showSearch = false;
},
toggleSearch() {
Expand All @@ -146,6 +157,20 @@ export default defineComponent({
});
}
},
async fetchLanguages() {
// Check if the collection parameter is available in the route
this.collectionShortName = this.$route.params.collection as string;
if (this.collectionShortName) {
// Fetch languages for the specific collection
try {
// @ts-ignore
this.langs = await this.$chronosAPI.getLangs(this.collectionShortName);
} catch (error) {
console.error(`Error fetching languages for collection ${this.collectionShortName}:`, error);
}
}
},
},
});
</script>
57 changes: 57 additions & 0 deletions src/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,60 @@ p span.badge {
.dropdown-menu {
min-width: 0;
}

.no-gap {
gap: 0;
}

.flex-grid>div>.card {
height: 100%;
}

.flex-grid>div>.card>.card-content {
height: 100%;
display: flex;
flex-direction: column;
}

.flex-grid>div>.card>.card-content>.content {
flex: 1;
}

.sidebar ul.menu-list li::before {
content: "";
width: 10px;
height: 1px;
background: #a3a3a3;
display: block;
position: absolute;
margin: 18px 0 0 0;
left: 0;
}

.sidebar ul.menu-list li.indent-2::before {
width: 5px;
}

.sidebar ul.menu-list li.indent-3:before {
width: 10px;
}

.sidebar ul.menu-list li.indent-4::before {
width: 15px;
}

.sidebar ul.menu-list li.indent-5::before {
width: 20px;
}

.panel-scrollable {
min-height: 120px;
}

.panel-block .block {
margin-bottom: 0;
}

.panel-block-header.panel-block-header--has-icon+.panel-block-content {
margin-left: 28px;
}
81 changes: 56 additions & 25 deletions src/core/chronos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import type { Article, ArticlesResponse, SearchResponse, ChronosConfig } from "@

interface ChronosAPI {
config(): Promise<ChronosConfig>;
getStatus(): Promise<string>;
getLangs(): Promise<string[]>;
getArticles(lang: string): Promise<ArticlesResponse>;
getArticleByLanguageAndSlug(lang: string, slug: string): Promise<Article | null>;
searchArticles(lang: string, query: string): Promise<SearchResponse>;
getStatus(collectionShortName: string): Promise<string>;
getLangs(collectionShortName: string): Promise<string[]>;
getArticles(lang: string, collectionShortName: string): Promise<ArticlesResponse>;
countArticles(lang: string, collectionShortName: string): Promise<number>;
getArticleByLanguageAndSlug(lang: string, slug: string, collectionShortName: string): Promise<Article | null>;
searchArticles(lang: string, query: string, collectionShortName: string): Promise<SearchResponse>;
}

const findCollectionByUrl = (config: ChronosConfig, url: string) => {
return config.chronosCollections.find(collection => collection.url === url);
};

const ChronosPlugin = {
install: async (app: App) => {
Expand All @@ -23,59 +27,86 @@ const ChronosPlugin = {
throw new Error("Failed to fetch Chronos configuration");
}
},
async getStatus() {

async getStatus(collectionShortName: string) {
try {
const config = await api.config();
const baseURL = config.chronosApiUrl;
const response = await axios.get(baseURL);
const collection = config.chronosCollections.find(c => c.shortName === collectionShortName);
if (!collection) {
throw new Error(`Collection with shortName ${collectionShortName} not found`);
}

const response = await axios.get(collection.url);
return response.data.status;
} catch (error) {
throw new Error('Failed to get server status');
}
},

async getLangs() {
async getLangs(collectionShortName: string) {
try {
const config = await api.config();
const baseURL = config.chronosApiUrl;
const response = await axios.get(`${baseURL}/langs`);
const collection = config.chronosCollections.find(c => c.shortName === collectionShortName);
if (!collection) {
throw new Error(`Collection with shortName ${collectionShortName} not found`);
}

const response = await axios.get(`${collection.url}/langs`);
return response.data;
} catch (error) {
throw new Error('Failed to get langs');
}
},

async getArticles(lang: string) {
async getArticles(lang: string, collectionShortName: string) {
try {
const config = await api.config();
const baseURL = config.chronosApiUrl;
const response = await axios.get(`${baseURL}/articles/${lang}`);
const collection = config.chronosCollections.find(c => c.shortName === collectionShortName);
if (!collection) {
throw new Error(`Collection with shortName ${collectionShortName} not found`);
}

const response = await axios.get(`${collection.url}/articles/${lang}`);
return response.data;
} catch (error) {
throw new Error('Failed to get articles');
}
},

async getArticleByLanguageAndSlug(lang: string, slug: string) {
async countArticles(lang: string, collectionShortName: string) {
try {
const articles = await api.getArticles(lang, collectionShortName);
return articles.articles.length;
} catch (error) {
throw new Error('Failed to count articles');
}
},

async getArticleByLanguageAndSlug(lang: string, slug: string, collectionShortName: string) {
try {
const config = await api.config();
const baseURL = config.chronosApiUrl;
const response = await axios.get(`${baseURL}/articles/${lang}/${slug}`);
return response.data;
} catch (error: any) {
if (error.response.status === 404) {
return null;
const collection = config.chronosCollections.find(c => c.shortName === collectionShortName);
if (!collection) {
throw new Error(`Collection with shortName ${collectionShortName} not found`);
}

const response = await axios.get(`${collection.url}/articles/${lang}/${slug}`);
return response.data;
} catch (error) {
throw new Error('Failed to get article');
}
},

async searchArticles(lang: string, query: string): Promise<SearchResponse> {
async searchArticles(lang: string, query: string, collectionShortName: string): Promise<SearchResponse> {
try {
const config = await api.config();
const baseURL = config.chronosApiUrl;
const response = await axios.get(`${baseURL}/search/${lang}?q=${query}`);
console.log(`${baseURL}/search/${lang}?q=${query}`);
const collection = config.chronosCollections.find(c => c.shortName === collectionShortName);
if (!collection) {
throw new Error(`Collection with shortName ${collectionShortName} not found`);
}

const response = await axios.get(`${collection.url}/search/${lang}?q=${query}`);
console.log(`${collection.url}/search/${lang}?q=${query}`);
return response.data;
} catch (error) {
throw new Error('Failed to search articles');
Expand Down
11 changes: 9 additions & 2 deletions src/core/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ interface ChronosConfig {
logoUrl: string;
description: string;
baseURL: string;
chronosApiUrl: string;
chronosCollections: ChronosCollection[];
extraLinks: ExtraLink[];
}

interface ChronosCollection {
shortName: string;
title: string;
url: string;
description: string;
}

interface ExtraLink {
url: string;
name: string;
}

export type { Article, SearchResponse, ArticlesResponse, ChronosConfig, ExtraLink };
export type { Article, SearchResponse, ArticlesResponse, ChronosConfig, ChronosCollection, ExtraLink };
8 changes: 7 additions & 1 deletion src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import ArticleView from "../views/ArticleView.vue";
import CollectionView from "../views/CollectionView.vue";
import { useHead } from 'unhead'


Expand All @@ -13,7 +14,12 @@ const router = createRouter({
component: HomeView,
},
{
path: "/:lang/:slug",
path: "/collections/:collection",
name: "collection",
component: CollectionView,
},
{
path: "/:collection/:lang/:slug",
name: "article",
component: ArticleView,
},
Expand Down
Loading

0 comments on commit 3759613

Please sign in to comment.