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
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ services:
- ./packages/web-app-importer/dist:/web/apps/importer
- ./packages/web-app-json-viewer/dist:/web/apps/json-viewer
- ./packages/web-app-maps/dist:/web/apps/maps
- ./packages/web-app-notes/dist:/web/apps/notes
- ./packages/web-app-progress-bars/dist:/web/apps/progress-bars
- ./packages/web-app-unzip/dist:/web/apps/unzip
depends_on:
Expand Down
1 change: 1 addition & 0 deletions packages/web-app-notes/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
Empty file.
20 changes: 20 additions & 0 deletions packages/web-app-notes/extension.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference types="vite/client" />

// FIXME: remove when bumping vue3-gettext to v4
declare module 'vue3-gettext' {
export function useGettext(): {
$gettext: (
msgid: string,
parameters?: {
[key: string]: string
},
disableHtmlEscaping?: boolean
) => string
}
}

// FIXME: remove when extension-sdk provides its own types
declare module '@opencloud-eu/extension-sdk' {
const defineConfig: (config: any) => void
export { defineConfig }
}
10 changes: 10 additions & 0 deletions packages/web-app-notes/l10n/.tx/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com

[o:opencloud-eu:p:opencloud-eu:r:web-extensions-notes]
file_filter = locale/<lang>/app.po
minimum_perc = 0
resource_name = web-extensions-notes
source_file = template.pot
source_lang = en
type = PO
1 change: 1 addition & 0 deletions packages/web-app-notes/l10n/translations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
26 changes: 26 additions & 0 deletions packages/web-app-notes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "web-app-notes",
"version": "0.0.0",
"private": true,
"description": "Your friendly neighborhood notes taking app.",
"license": "AGPL-3.0",
"type": "module",
"scripts": {
"build": "pnpm vite build",
"build:w": "pnpm vite build --watch --mode development",
"check:types": "vue-tsc --noEmit",
"test:unit": "NODE_OPTIONS=--unhandled-rejections=throw vitest"
},
"devDependencies": {
"pinia": "3.0.4",
"vue": "^3.4.21",
"vue-concurrency": "^5.0.3",
"vue3-gettext": "^2.4.0",
"zod": "^4.3.0"
},
"peerDependencies": {
"@opencloud-eu/design-system": "^5.0.0-alpha.1",
"@opencloud-eu/web-client": "^5.0.0-alpha.1",
"@opencloud-eu/web-pkg": "^5.0.0-alpha.1"
}
}
41 changes: 41 additions & 0 deletions packages/web-app-notes/src/components/ActionIconButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<OcButton
Comment thread
kulmann marked this conversation as resolved.
v-oc-tooltip="action.label(actionOptions)"
appearance="raw"
:aria-label="action.label(actionOptions)"
:disabled="action.isDisabled?.(actionOptions) || false"
@click="() => action.handler(actionOptions)"
>
<OcIcon
:name="typeof action.icon === 'function' ? action.icon(actionOptions) : action.icon"
fill-type="line"
/>
</OcButton>
</template>

<script setup lang="ts">
import { Resource } from '@opencloud-eu/web-client'
import { useNotebookStore } from '../composables'
import { computed } from 'vue'
import { Action } from '@opencloud-eu/web-pkg'

const { action, resource } = defineProps<{
action: Action
resource: Resource
}>()
const notebookStore = useNotebookStore()

const actionOptions = computed(() => {
return {
space: notebookStore.space,
resources: [resource]
}
})

/**
* FIXME:
* we could use ActionMenuItem as template instead of a custom template, if ActionMenuItem would not
* be wrapped in an `<li>`. For reference:
* `<ActionMenuItem :action="{ ...action, hideLabel: true }" :action-options="actionOptions" />`
*/
</script>
19 changes: 19 additions & 0 deletions packages/web-app-notes/src/components/NoPageSelected.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<div class="ext:w-full ext:h-full ext:flex ext:items-center ext:justify-center">
<div class="ext:flex ext:items-center ext:justify-center ext:gap-3">
<OcIcon name="edit-box" size="xlarge" fill-type="line" />
<div class="ext:max-w-[200px]">
<h2 class="ext:my-0">{{ $gettext('Your notes') }}</h2>
<p class="ext:mb-0">
{{ $gettext('Please create or select a note on the left.') }}
</p>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { useGettext } from 'vue3-gettext'

const { $gettext } = useGettext()
</script>
43 changes: 43 additions & 0 deletions packages/web-app-notes/src/components/TocContextActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<div>
<OcButton
:id="`toc-contextmenu-trigger-${extractDomSelector(node.resource.id)}`"
appearance="raw"
class="ext:py-1"
>
<OcIcon name="more-2" />
</OcButton>
<OcDrop
:drop-id="`toc-contextmenu-${extractDomSelector(node.resource.id)}`"
mode="click"
padding-size="small"
:toggle="`#toc-contextmenu-trigger-${extractDomSelector(node.resource.id)}`"
close-on-click
:title="node.resource.name"
@click.stop.prevent
>
<ContextActionMenu :menu-sections="menuSections" :action-options="actionOptions" />
</OcDrop>
</div>
</template>

<script setup lang="ts">
import { ContextActionMenu, type MenuSection } from '@opencloud-eu/web-pkg'
import { TocNode } from '../types'
import { extractDomSelector } from '@opencloud-eu/web-client'
import { useNotebookStore } from '../composables'
import { computed } from 'vue'

const { node, menuSections } = defineProps<{
node: TocNode
menuSections: MenuSection[]
}>()
const notebookStore = useNotebookStore()

const actionOptions = computed(() => {
return {
space: notebookStore.space,
resources: [node.resource]
}
})
</script>
38 changes: 38 additions & 0 deletions packages/web-app-notes/src/components/TocEmpty.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<template>
<div class="ext:w-full ext:flex ext:justify-between">
<div>
<span v-if="hasWriteAccess">{{ $gettext('Create your first note.') }}</span>
<span v-else>{{ $gettext('There are no notes in this notebook, yet.') }}</span>
</div>
<div v-if="hasWriteAccess" class="ext:min-w-[77px] ext:h-[3rem] ext:-mt-5" :aria-hidden="true">
<svg
viewBox="0 0 77 50"
width="100%"
height="100%"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
preserveAspectRatio="none"
>
<!-- Horizontal stroke taking full width -->
<!-- Goes right, then curves up slightly and points to the top -->
<path d="M 0 38 H 52 Q 66 38 66 24 V 10" />
<!-- Arrow head pointing upward at the end of the curve -->
<path d="M 66 10 L 60 18" />
<path d="M 66 10 L 70 18" />
</svg>
</div>
</div>
</template>

<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import { useNotebookStore } from '../composables'
import { storeToRefs } from 'pinia'

const { $gettext } = useGettext()
const notebookStore = useNotebookStore()
const { hasWriteAccess } = storeToRefs(notebookStore)
</script>
97 changes: 97 additions & 0 deletions packages/web-app-notes/src/components/TocFile.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<template>
<div class="ext:w-100 toc-item-wrapper">
<div>
<OcButton
appearance="raw"
class="ext:p-1 ext:w-full ext:text-left"
:class="{ 'ext:font-semibold': node.resource.id === documentId }"
justify-content="start"
@click="() => openDocument(node)"
>
<OcIcon name="article" fill-type="line" class="ext:text-neutral-500" />
{{ extractNameWithoutExtension(node.resource) }}
</OcButton>
</div>
<div class="ext:flex ext:nowrap ext:items-center ext:gap-1">
<ActionIconButton
v-for="action of getFileQuickActions(node)"
:key="`file-quick-action-${action.name}-${extractDomSelector(node.resource.id)}`"
:resource="node.resource"
:action="action"
/>
<TocContextActions :node="node" :menu-sections="getFileMenuSections(node)" />
</div>
</div>
</template>

<script setup lang="ts">
import ActionIconButton from './ActionIconButton.vue'
import TocContextActions from './TocContextActions.vue'
import { extractDomSelector, extractNameWithoutExtension } from '@opencloud-eu/web-client'
import { TocNode } from '../types'
import {
buildDocumentRoute,
useActionsOpenDocument,
useActionsSaveCurrentDocument,
useDocumentStore,
useNotebookStore
} from '../composables'
import { storeToRefs } from 'pinia'
import {
Action,
type MenuSection,
useFileActionsDelete,
useFileActionsRename,
useRouter
} from '@opencloud-eu/web-pkg'
import { unref } from 'vue'

const router = useRouter()

const { node } = defineProps<{
node: TocNode
}>()

const notebookStore = useNotebookStore()
const documentStore = useDocumentStore()
const { documentId } = storeToRefs(documentStore)

const openDocument = async (node: TocNode) => {
await router.push(buildDocumentRoute(notebookStore.space, notebookStore.notebook, node))
}

const { actions: actionsRename } = useFileActionsRename()
const { actions: actionsDelete } = useFileActionsDelete()
const { actions: actionsOpenDocument } = useActionsOpenDocument(node)
const { actions: actionsSaveDocument } = useActionsSaveCurrentDocument(node)

const getActionOptions = (node: TocNode) => ({
space: notebookStore.space,
resources: [node.resource]
})

const getFileQuickActions = (node: TocNode): Action[] => {
return [...actionsSaveDocument].filter((action) => action.isVisible(getActionOptions(node)))
}
const getFileMenuSections = (node: TocNode): MenuSection[] => {
const items: Action[] = [
...actionsOpenDocument,
...actionsSaveDocument,
...unref(actionsRename),
...unref(actionsDelete)
].filter((action) => action.isVisible(getActionOptions(node)))
return [
{
name: 'file-actions',
items
}
]
}
</script>

<style scoped>
.toc-item-wrapper {
display: grid;
grid-template-columns: 1fr auto;
}
</style>
Loading