Skip to content
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
5 changes: 5 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ Due to my Bachelors' thesis I took a 3 week break from Torii here and `dev9` sho

### `v0.0.0-dev11`

- [ ] Continue Client Refactoring: Split the project features into composables
- [ ] `useProjectFiles()`
- [ ] `useCurrentWorkspace()`
- [ ] `useCurrentRecord()`
- [ ] `useProjectSearch()`
- [ ] Client Documentation (Github CI)
- [ ] [clean-jsdoc-theme](https://www.npmjs.com/package/@clean-jsdoc-theme/typedoc)
- [ ] Remove Naive UI dependency and implement custom vue components
Expand Down
47 changes: 47 additions & 0 deletions torii-client/plugin-recent-projects/src/AddRecentProject.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<button class="add-recent-project" @click="openProjectDialog">
{{ $t('app.project.open') }}
</button>
</template>

<script lang="ts" setup>
import { open } from '@tauri-apps/plugin-dialog';
import { addRecentProject } from './service';

/**
* @brief Opens a dialog to open a project folder from file system.
*/
async function openProjectDialog() {
const selected = await open({
directory: true,
multiple: false,
title: 'Select a project folder',
});

if (!selected || typeof selected !== 'string') return;

const name = selected.split('/').pop() || 'Unknown Project';
addRecentProject({ path: selected, name });
}
</script>

<style lang="scss" scoped>
.add-recent-project {
padding: 8px 12px;
border: none;
background-color: #f0f0f0;
border-radius: 4px;
text-align: left;
cursor: pointer;

&:hover {
background-color: #e0e0e0;
}

&:disabled {
background-color: #f0f0f0;
color: #999;
cursor: not-allowed;
}
}
</style>
90 changes: 90 additions & 0 deletions torii-client/plugin-recent-projects/src/RecentProjectItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<div class="recent-project-item" @click="openProject()" @keydown.space.enter="openProject()">
<span class="project-title">{{ project.name }}</span>
<n-dropdown
trigger="hover"
placement="right"
:options="projectOptions"
:show-arrow="true"
@select="(_, option) => handleProjectOption(option)"
>
<n-icon class="project-remove">
<ellipsis-vertical />
</n-icon>
</n-dropdown>
</div>
</template>

<script setup lang="ts">
import { RecentProject, removeRecentProject } from './service';
import { NIcon, NDropdown, DropdownOption } from 'naive-ui';
import { EllipsisVertical } from '@vicons/ionicons5';
import { computed } from 'vue';

const props = defineProps<{
project: RecentProject;
}>();
const emit = defineEmits<{
open: [RecentProject];
}>();

/**
* @brief The project options, which appear in the dropdown. To specify behaviour
* based on the dropdown action, refer to {@link handleProjectOption}.
*/
const projectOptions = computed<DropdownOption[]>(() => {
if (props.project.is_system) {
return [{ label: 'Remove', key: 'remove', disabled: true }];
}

return [{ label: 'Remove', key: 'remove' }];
});

/**
* @param project Project Metadata
* @param option Project Option
*/
function handleProjectOption(option: DropdownOption) {
switch (option.key) {
case 'remove':
removeRecentProject(props.project.path);
break;
default:
console.error('Not Implemented: Received drop down action:', option.key, option);
}
}

/**
* @brief Opens the project for the current recent project item.
*/
function openProject() {
emit('open', props.project);
}
</script>

<style lang="scss" scoped>
.recent-project-item {
display: flex;
flex-direction: row;
align-items: center;
height: 2em;
cursor: pointer;

&:hover {
background-color: #eee;
}

.project-title {
flex: 1;
}

.project-remove {
cursor: pointer;
color: #999;

&:hover {
color: #666;
}
}
}
</style>
117 changes: 10 additions & 107 deletions torii-client/plugin-recent-projects/src/RecentProjects.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,86 +4,33 @@
{{ $t('app.project.noRecent') }}
</span>
<div v-else class="project-list">
<span
:tabindex="index"
@keydown.space.enter="openProject(project.path)"
class="project-entry"
<RecentProjectItem
:tabindex="1"
v-for="(project, index) in recentProjects"
:key="project.path"
>
<span @click="openProject(project.path)" class="project-title">{{ project.name }}</span>
<n-dropdown
trigger="hover"
placement="right"
:options="getProjectOptions(project)"
:show-arrow="true"
@select="(_, option) => handleProjectOptions(project, option)"
>
<n-icon class="project-remove">
<ellipsis-vertical />
</n-icon>
</n-dropdown>
</span>
:project="project"
@open="openProject(project.path)"
/>
</div>
<button @click="openProjectDialog">
{{ $t('app.project.open') }}
</button>
<AddRecentProject />
</div>
</template>

<script setup lang="ts">
import { listRecentProjects, addRecentProject, removeRecentProject } from './api.ts';
import { open } from '@tauri-apps/plugin-dialog';
import { NIcon, NDropdown, DropdownOption } from 'naive-ui';
import { EllipsisVertical } from '@vicons/ionicons5';
import { listRecentProjects } from './service.ts';
import { useRouter } from 'vue-router';
import AddRecentProject from './AddRecentProject.vue';
import RecentProjectItem from './RecentProjectItem.vue';

const router = useRouter();
const recentProjects = listRecentProjects();

function getProjectOptions(project: any): DropdownOption[] {
if (project.is_system) {
return [{ label: 'Remove', key: 'remove', disabled: true }];
}

return [{ label: 'Remove', key: 'remove' }];
}

/**
* Opens a dialog to open a project folder from file system.
*/
async function openProjectDialog() {
const selected = await open({
directory: true,
multiple: false,
title: 'Select a project folder',
});

if (!selected || typeof selected !== 'string') return;

const name = selected.split('/').pop() || 'Unknown Project';
addRecentProject({ path: selected, name });
}

/**
* Opens the project at the given path.
* @brief Opens the project at the given path.
*/
function openProject(projectPath: string) {
router.push({ name: 'project', query: { project: projectPath } });
}

/**
* @param project Project Metadata
* @param option Project Option
*/
function handleProjectOptions(project: { path: string; name: string }, option: DropdownOption) {
console.log('Selected option', option, 'for project', project);
switch (option.key) {
case 'remove':
removeRecentProject(project.path);
break;
}
}
</script>

<style lang="scss" scoped>
Expand All @@ -100,50 +47,6 @@ function handleProjectOptions(project: { path: string; name: string }, option: D
display: flex;
flex-direction: column;
gap: 8px;

.project-entry {
display: flex;
flex-direction: row;
align-items: center;
height: 2em;
cursor: pointer;

&:hover {
background-color: #eee;
}

.project-title {
flex: 1;
}

.project-remove {
cursor: pointer;
color: #999;

&:hover {
color: #666;
}
}
}

button {
padding: 8px 12px;
border: none;
background-color: #f0f0f0;
border-radius: 4px;
text-align: left;
cursor: pointer;

&:hover {
background-color: #e0e0e0;
}

&:disabled {
background-color: #f0f0f0;
color: #999;
cursor: not-allowed;
}
}
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Ref, ref } from 'vue';
/**
* @brief Type of a recent project struct.
*/
interface RecentProject {
export interface RecentProject {
/**
* The name of the project.
*/
Expand All @@ -24,7 +24,13 @@ interface RecentProject {
* The timestamp of when the project was last opened, in milliseconds since
* the Unix epoch.
*/
lastOpened?: number;
last_opened?: number;

/**
* Wether the project is added by the system. Such projects are not stored
* in the recent projects file and cannot be removed.
*/
is_system?: boolean;
}

const recentProjects = ref<RecentProject[]>([]);
Expand Down
27 changes: 6 additions & 21 deletions torii-client/webapp/src/components/article/MarkdownEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { invoke } from '@tauri-apps/api/core';
import { Editor, EditorContent } from '@tiptap/vue-3';
import { Markdown } from '@tiptap/markdown';
import { TaskList, TaskItem } from '@tiptap/extension-list';
Expand All @@ -39,6 +38,7 @@ import {
} from './autocomplete-extension.ts';
import MarkdownEditorAutocomplete from './MarkdownEditorAutocomplete.vue';
import type { Record } from '../../types';
import { getRecordComponent, saveRecordComponent } from '@/services/recordsService.ts';

const props = defineProps<{
record: Record;
Expand Down Expand Up @@ -186,26 +186,17 @@ watch(
if (!record) return;

// Load the file content when a new file is selected
let bytes = await invoke('get_record_component', {
record: props.record,
component: 'article',
});
let content = new TextDecoder().decode(bytes as Uint8Array);
// console.debug('load', content);
let bytes = await getRecordComponent<Uint8Array>(props.record, 'article');
let content = new TextDecoder().decode(bytes);

ignoreFirstSave.value = true;
editor.commands.setContent(content, { contentType: 'markdown' });
}
);

async function loadFile() {
let bytes = await invoke('get_record_component', {
record: props.record,
component: 'article',
});
// console.log(bytes);
let content = new TextDecoder().decode(bytes as Uint8Array);
// console.debug('load', content);
let bytes = await getRecordComponent<Uint8Array>(props.record, 'article');
let content = new TextDecoder().decode(bytes);

ignoreFirstSave.value = true;
editor.commands.setContent(content, { contentType: 'markdown' });
Expand All @@ -220,15 +211,9 @@ async function saveFile() {

// Get markdown content from the editor
const content = editor.getMarkdown();
// console.debug('save', content);

// Save the file content whenever it changes
await invoke('save_record_component', {
record: props.record,
component: 'article',
content,
contentType: 'text/markdown',
});
await saveRecordComponent(props.record, 'article', content, 'text/markdown');
}

// Compute word count from the editor (e.g., on update)
Expand Down
Loading
Loading