mirror of
https://github.com/JanGross/quicknotes.git
synced 2025-11-30 23:37:16 +01:00
Add dashboard widget to show the latest notes. Issue #51
p.s: I would like to be able to select a particular tag to show, but none of the dashboard widgets are configurable. I hate increasing the app size from 300k to almost 3 mb for something so simple, but we must adapt to the majority and use vue here.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
build
|
build
|
||||||
js/templates.js
|
js/templates.js
|
||||||
|
js/quicknotes-dashboard.js*
|
||||||
node_modules
|
node_modules
|
||||||
vendor
|
vendor
|
||||||
translationfiles/
|
translationfiles/
|
||||||
|
|||||||
17
Makefile
17
Makefile
@@ -26,8 +26,8 @@ package_name=$(app_name)
|
|||||||
cert_dir=$(HOME)/.nextcloud/certificates
|
cert_dir=$(HOME)/.nextcloud/certificates
|
||||||
|
|
||||||
# building the javascript
|
# building the javascript
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
build: deps
|
|
||||||
|
|
||||||
# L10N Rules
|
# L10N Rules
|
||||||
|
|
||||||
@@ -77,9 +77,19 @@ depsmin:
|
|||||||
cp node_modules/medium-editor-autolist/dist/autolist.min.js vendor/autolist.js
|
cp node_modules/medium-editor-autolist/dist/autolist.min.js vendor/autolist.js
|
||||||
cp node_modules/lozad/dist/lozad.min.js vendor/lozad.js
|
cp node_modules/lozad/dist/lozad.min.js vendor/lozad.js
|
||||||
|
|
||||||
|
|
||||||
|
# Build Rules
|
||||||
|
build-vue:
|
||||||
|
npm run build
|
||||||
|
|
||||||
js-templates:
|
js-templates:
|
||||||
node_modules/handlebars/bin/handlebars js/templates -f js/templates.js
|
node_modules/handlebars/bin/handlebars js/templates -f js/templates.js
|
||||||
|
|
||||||
|
build: depsmin js-templates build-vue
|
||||||
|
@echo ""
|
||||||
|
@echo "Build done. You can enable the application in Nextcloud."
|
||||||
|
|
||||||
|
# Clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(build_dir)
|
rm -rf $(build_dir)
|
||||||
|
|
||||||
@@ -91,7 +101,7 @@ distclean: clean
|
|||||||
|
|
||||||
dist: appstore
|
dist: appstore
|
||||||
|
|
||||||
appstore: distclean depsmin
|
appstore: distclean build
|
||||||
mkdir -p $(sign_dir)
|
mkdir -p $(sign_dir)
|
||||||
rsync -a \
|
rsync -a \
|
||||||
--exclude='.*' \
|
--exclude='.*' \
|
||||||
@@ -107,8 +117,11 @@ appstore: distclean depsmin
|
|||||||
--exclude=vendor/bin \
|
--exclude=vendor/bin \
|
||||||
--exclude=node_modules \
|
--exclude=node_modules \
|
||||||
--exclude=js/templates \
|
--exclude=js/templates \
|
||||||
|
--exclude=src \
|
||||||
--exclude=templates/fake.php \
|
--exclude=templates/fake.php \
|
||||||
--exclude=translation* \
|
--exclude=translation* \
|
||||||
|
--exclude=webpack*.js \
|
||||||
|
--exclude=psalm.xml \
|
||||||
$(project_dir) $(sign_dir)
|
$(project_dir) $(sign_dir)
|
||||||
@echo "Signing…"
|
@echo "Signing…"
|
||||||
php ../../occ integrity:sign-app \
|
php ../../occ integrity:sign-app \
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ return ['resources' =>
|
|||||||
'url' => '/',
|
'url' => '/',
|
||||||
'verb' => 'GET'
|
'verb' => 'GET'
|
||||||
],
|
],
|
||||||
|
// Dashboard
|
||||||
|
[
|
||||||
|
'name' => 'note#dashboard',
|
||||||
|
'url' => '/notes/dashboard',
|
||||||
|
'verb' => 'GET',
|
||||||
|
],
|
||||||
// Share
|
// Share
|
||||||
[
|
[
|
||||||
'name' => 'share#destroy',
|
'name' => 'share#destroy',
|
||||||
|
|||||||
7
css/global.scss
Normal file
7
css/global.scss
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.icon-quicknotes {
|
||||||
|
@include icon-color('app', 'quicknotes', $color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pinned {
|
||||||
|
@include icon-color('pinned', 'quicknotes', $color-black);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
/*
|
/*
|
||||||
* @copyright 2016-2021 Matias De lellis <mati86dl@gmail.com>
|
* @copyright 2016-2022 Matias De lellis <mati86dl@gmail.com>
|
||||||
*
|
*
|
||||||
* @author 2016 Matias De lellis <mati86dl@gmail.com>
|
* @author 2016 Matias De lellis <mati86dl@gmail.com>
|
||||||
*
|
*
|
||||||
@@ -27,6 +27,8 @@ use OCP\AppFramework\Bootstrap\IBootstrap;
|
|||||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||||
|
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use OCP\IServerContainer;
|
use OCP\IServerContainer;
|
||||||
@@ -48,6 +50,11 @@ class Application extends App implements IBootstrap {
|
|||||||
public function register(IRegistrationContext $context): void {
|
public function register(IRegistrationContext $context): void {
|
||||||
$context->registerSearchProvider(NoteSearchProvider::class);
|
$context->registerSearchProvider(NoteSearchProvider::class);
|
||||||
$context->registerCapability(Capabilities::class);
|
$context->registerCapability(Capabilities::class);
|
||||||
|
$context->registerDashboardWidget(DashboardWidget::class);
|
||||||
|
$context->registerEventListener(
|
||||||
|
BeforeTemplateRenderedEvent::class,
|
||||||
|
BeforeTemplateRenderedListener::class
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(IBootContext $context): void {
|
public function boot(IBootContext $context): void {
|
||||||
|
|||||||
26
lib/AppInfo/BeforeTemplateRenderedListener.php
Normal file
26
lib/AppInfo/BeforeTemplateRenderedListener.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\QuickNotes\AppInfo;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||||
|
use OCP\EventDispatcher\Event;
|
||||||
|
use OCP\EventDispatcher\IEventListener;
|
||||||
|
|
||||||
|
class BeforeTemplateRenderedListener implements IEventListener {
|
||||||
|
|
||||||
|
public function handle(Event $event): void {
|
||||||
|
|
||||||
|
if (!($event instanceof BeforeTemplateRenderedEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$event->isLoggedIn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
\OCP\Util::addStyle('quicknotes', 'global');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
64
lib/AppInfo/DashboardWidget.php
Normal file
64
lib/AppInfo/DashboardWidget.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\QuickNotes\AppInfo;
|
||||||
|
|
||||||
|
use OCP\Dashboard\IWidget;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\IURLGenerator;
|
||||||
|
|
||||||
|
class DashboardWidget implements IWidget {
|
||||||
|
|
||||||
|
private IURLGenerator $url;
|
||||||
|
private IL10N $l10n;
|
||||||
|
|
||||||
|
public function __construct(IURLGenerator $url,
|
||||||
|
IL10N $l10n)
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getId(): string {
|
||||||
|
return 'quicknotes';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getTitle(): string {
|
||||||
|
return $this->l10n->t('Quick notes');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getOrder(): int {
|
||||||
|
return 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getIconClass(): string {
|
||||||
|
return 'icon-quicknotes';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUrl(): ?string {
|
||||||
|
return $this->url->linkToRouteAbsolute('quicknotes.page.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function load(): void {
|
||||||
|
\OCP\Util::addScript('quicknotes', 'quicknotes-dashboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -67,6 +67,41 @@ class NoteController extends Controller {
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function dashboard(): JSONResponse {
|
||||||
|
$notes = $this->noteService->getAll($this->userId);
|
||||||
|
if (count($notes) === 0) {
|
||||||
|
return new JSONResponse([
|
||||||
|
'notes' => []
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = array_map(function ($note) {
|
||||||
|
return [
|
||||||
|
'id' => $note->getId(),
|
||||||
|
'title' => strip_tags($note->getTitle()),
|
||||||
|
'content' => strip_tags($note->getContent()),
|
||||||
|
'pinned' => $note->getIsPinned(),
|
||||||
|
'timestamp' => $note->getTimestamp(),
|
||||||
|
];
|
||||||
|
}, $notes);
|
||||||
|
|
||||||
|
usort($items, function ($a, $b) {
|
||||||
|
if ($a['pinned'] == $b['pinned'])
|
||||||
|
return $b['timestamp'] - $a['timestamp'];
|
||||||
|
if ($a['pinned'] && !$b['pinned'])
|
||||||
|
return -1;
|
||||||
|
if (!$a['pinned'] && $b['pinned'])
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return new JSONResponse([
|
||||||
|
'notes' => array_slice($items, 0, 7)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
|
|||||||
21654
package-lock.json
generated
21654
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
69
package.json
69
package.json
@@ -1,30 +1,43 @@
|
|||||||
{
|
{
|
||||||
"name": "quicknotes",
|
"name": "quicknotes",
|
||||||
"version": "0.1.9",
|
"version": "0.8.1",
|
||||||
"description": "Quick notes with a basic rich text",
|
"description": "Quick notes with a basic rich text",
|
||||||
"main": "script.js",
|
"main": "script.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"doc": "doc",
|
"doc": "doc",
|
||||||
"test": "tests"
|
"test": "tests"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"build": "webpack --node-env production --progress"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/matiasdelellis/quicknotes.git"
|
"url": "git+https://github.com/matiasdelellis/quicknotes.git"
|
||||||
},
|
},
|
||||||
"author": "Matias De lellis <mati86dl@gmail.com>",
|
"author": "Matias De lellis <mati86dl@gmail.com>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/matiasdelellis/quicknotes/issues"
|
"url": "https://github.com/matiasdelellis/quicknotes/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/matiasdelellis/quicknotes#readme",
|
"homepage": "https://github.com/matiasdelellis/quicknotes#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"handlebars": "^4.5.1",
|
"handlebars": "^4.5.1",
|
||||||
"isotope-layout": "^3.0.6",
|
"isotope-layout": "^3.0.6",
|
||||||
"lozad": "^1.15.0",
|
"lozad": "^1.15.0",
|
||||||
"medium-editor": "^5.23.3",
|
"medium-editor": "^5.23.3",
|
||||||
"medium-editor-autolist": "^1.0.1"
|
"medium-editor-autolist": "^1.0.1",
|
||||||
}
|
"@nextcloud/axios": "^1.9.0",
|
||||||
|
"@nextcloud/dialogs": "^3.1.2",
|
||||||
|
"@nextcloud/router": "^2.0.0",
|
||||||
|
"@nextcloud/vue": "^5.3.1",
|
||||||
|
"@nextcloud/vue-dashboard": "^2.0.1",
|
||||||
|
"vue": "^2.6.14"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nextcloud/babel-config": "^1.0.0",
|
||||||
|
"@nextcloud/browserslist-config": "^2.2.0",
|
||||||
|
"@nextcloud/eslint-config": "^7.0.2",
|
||||||
|
"@nextcloud/stylelint-config": "^2.1.2",
|
||||||
|
"@nextcloud/webpack-vue-config": "^5.1.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/NotesService.js
Normal file
21
src/NotesService.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { showError } from '@nextcloud/dialogs'
|
||||||
|
|
||||||
|
function url(url) {
|
||||||
|
url = `apps/quicknotes${url}`
|
||||||
|
return generateUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getDashboardData = () => {
|
||||||
|
return axios
|
||||||
|
.get(url('/notes/dashboard'))
|
||||||
|
.then(response => {
|
||||||
|
return response.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
showError(t('notes', 'Fetching notes for dashboard has failed.'))
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
107
src/components/Dashboard.vue
Normal file
107
src/components/Dashboard.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<DashboardWidget :items="items"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<template #default="{ item }">
|
||||||
|
<DashboardWidgetItem
|
||||||
|
:target-url="getItemTargetUrl(item)"
|
||||||
|
:main-text="item.title"
|
||||||
|
:sub-text="item.content"
|
||||||
|
>
|
||||||
|
<template #avatar>
|
||||||
|
<div
|
||||||
|
class="note-item"
|
||||||
|
:class="{ 'icon-pinned': item.pinned, 'note-item-no-pinned': !hasPinned }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</DashboardWidgetItem>
|
||||||
|
</template>
|
||||||
|
<template #empty-content>
|
||||||
|
<EmptyContent icon="icon-quicknotes">
|
||||||
|
<template #desc>
|
||||||
|
<p class="notes-empty-content-label">
|
||||||
|
{{ t('quicknotes', 'Nothing here. Take your first quick notes') }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a :href="createNoteUrl" class="button">{{ t('quicknotes', 'Create a note…') }}</a>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</EmptyContent>
|
||||||
|
</template>
|
||||||
|
</DashboardWidget>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { DashboardWidget, DashboardWidgetItem } from '@nextcloud/vue-dashboard'
|
||||||
|
import { EmptyContent } from '@nextcloud/vue'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
|
import { getDashboardData } from '../NotesService.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Dashboard',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
DashboardWidget,
|
||||||
|
DashboardWidgetItem,
|
||||||
|
EmptyContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
items: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasPinned() {
|
||||||
|
return this.items.length > 0 && this.items[0].pinned
|
||||||
|
},
|
||||||
|
|
||||||
|
createNoteUrl() {
|
||||||
|
return generateUrl('/apps/quicknotes')
|
||||||
|
},
|
||||||
|
|
||||||
|
getItemTargetUrl() {
|
||||||
|
return (note) => {
|
||||||
|
return generateUrl('/apps/quicknotes/?n=' + note.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.loadDashboardData()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
loadDashboardData() {
|
||||||
|
getDashboardData().then(data => {
|
||||||
|
this.items = data.notes
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.note-item {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
line-height: 44px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-size: 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-item-no-pinned {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes-empty-content-label {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
11
src/dashboard.js
Normal file
11
src/dashboard.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import Dashboard from './components/Dashboard.vue'
|
||||||
|
|
||||||
|
Vue.mixin({ methods: { t, n } })
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
OCA.Dashboard.register('quicknotes', (el) => {
|
||||||
|
const View = Vue.extend(Dashboard)
|
||||||
|
new View().$mount(el)
|
||||||
|
})
|
||||||
|
})
|
||||||
8
webpack.config.js
Normal file
8
webpack.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const webpackConfig = require('@nextcloud/webpack-vue-config')
|
||||||
|
|
||||||
|
webpackConfig.entry = {
|
||||||
|
dashboard: { import: path.join(__dirname, 'src', 'dashboard.js') }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = webpackConfig
|
||||||
Reference in New Issue
Block a user