Skip to content

Commit

Permalink
Merge pull request #73 from Kovah/dev
Browse files Browse the repository at this point in the history
v0.0.23
  • Loading branch information
Kovah committed Oct 15, 2019
2 parents 4d27c12 + 1e242f1 commit af5f64e
Show file tree
Hide file tree
Showing 37 changed files with 320 additions and 292 deletions.
33 changes: 22 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,33 @@

## About LinkAce

> @TODO Screenshot(s)
![Preview Screenshot](https://www.linkace.org/images/preview/linkace_dashboard.png)

LinkAce is a bookmark manager similar to Shaarli and other tools. I built this tool to have something that fits my
actual needs that other bookmark managers couldn't solve, even if most features are almost the same.

### Features

* Bookmark links with automatic title generation
* Bookmark links with automatic title and description generation
* Organize bookmarks in categories and tags
* A bookmarklet to quickly save links from any browser
* Private or public links so friends or internet stranges can see your collection
* Private or public links, so friends or internet strangers can see your collection
* Add notes to links to add thoughts
* Advanced search for your bookmarks
* A built-in light and dark color theme
* Import existing bookmarks from HTML exports (other methods planned)
* Automated link checks to make sure your bookmarks stay available
* Automated “backups” of your bookmarks via the Waybackmachine
* Implemented support for complete database and app backups to Amazon AWS S3

More features are already planned. Take a look at our [project board](https://github.com/Kovah/LinkAce/projects/1)
for more information.
for more information.

### Documentation and Community

Any further information about all the available features and how to install the app, can be found on the
[LinkAce Website](https://www.linkace.org/). Additionally, you may visit the [community forums](https://community.linkace.org/)
to share your ideas, talk with other users or find help for specific problems.


---
Expand Down Expand Up @@ -150,7 +160,7 @@ error_page 404 /index.php;
#### 4. Import a database dump to your Database

To be able to run the app you need to import a database dump into your database.
> @TODO
The latest database dump can be found [in the wiki](https://www.linkace.org/docs/v1/setup/setup-without-docker#4-import-a-database-dump-to-your-database).


---
Expand All @@ -161,11 +171,11 @@ If you need help or want to report a bug within the application, please open a n
and describe:

* which version you are using,
* what your problem is,
* and what you already done to solve the problem.
* what your exact problem is,
* and what you already did to solve the problem.

**Please notice**: This is a private side-project mainly developed for *myself*. Therefore I cannot guarantee that the
app will work without any problems and I also won't answer support requests within a short period of time.
app will work without any problems for you, and I may won't answer support requests within a short period of time.


---
Expand All @@ -180,6 +190,7 @@ Maybe there already is an existing ticket for your or a very similar topic.

* Always use the `dev` branch to work on the application. The dev branch will contain the latest version of the app
while the `master` branch may contains the stable version (which may be outdated in terms of development).
* Consider using a separate branch if you are working on a larger feature.
* When opening a pull request, link to your ticket and describe what you did to solve the problem.


Expand Down Expand Up @@ -212,12 +223,12 @@ npm install
OR
yarn install

./node_modules/.bin/grunt build
npm run dev
```

### 2. Working with the Artisan command line

I recommend using the Artisan command line tool in the PHP container only to make sure that the same environment is
I recommend using the Artisan command line tool in the PHP container only, to make sure that the same environment is
used. To do so, use the following example command:

```bash
Expand All @@ -235,7 +246,7 @@ docker exec -it linkace-php bash -c "php artisan registeruser [user name] [user

### Tests

You may run some existing tests with the following command:
You can run existing tests with the following command:

```bash
docker exec -it linkace-php bash -c "./vendor/bin/phpunit"
Expand Down
2 changes: 1 addition & 1 deletion config/linkace.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
return [
'version' => 'v0.0.22',
'version' => 'v0.0.23',

'default' => [
'pagination' => 25,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "linkace",
"version": "0.0.22",
"version": "0.0.23",
"description": "A small, selfhosted bookmark manager with advanced features, built with Laravel and Docker",
"homepage": "https://github.com/Kovah/LinkAce",
"repository": {
Expand Down
14 changes: 12 additions & 2 deletions resources/assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@ import { register } from './lib/views';
import Base from './components/Base';
import UrlField from './components/UrlField';
import LoadingButton from './components/LoadingButton';
import BookmarkTimer from './components/BookmarkTimer';
import TagsSelect from './components/TagsSelect';
import SimpleSelect from './components/SimpleSelect';
import ShareToggleAll from './components/ShareToggleAll';
import GenerateApiToken from './components/GenerateApiToken';
import GenerateCronToken from './components/GenerateCronToken';

// Register view components
function registerViews () {
register('#app', Base);
register('input[id="url"]', UrlField);
register('button[type="submit"]', LoadingButton);
register('.bm-timer', BookmarkTimer);
register('.tags-select', TagsSelect);
register('.simple-select', SimpleSelect);
register('.share-toggle', ShareToggleAll);
register('.api-token', GenerateApiToken);
register('.cron-token', GenerateCronToken);
}

if (document.readyState !== 'loading') {
// dom loaded event already fired
registerViews();
} else {
// wait for the dom to load
document.addEventListener('DOMContentLoaded', registerViews);
}
17 changes: 17 additions & 0 deletions resources/assets/js/components/BookmarkTimer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default class BookmarkTimer {

constructor ($el) {
this.$el = $el;
this.init();
}

init () {
window.setInterval(function () {
this.$el.text(parseInt(this.$el.text()) - 1);
}, 1000);

window.setTimeout(function () {
window.close();
}, 5000);
}
}
61 changes: 61 additions & 0 deletions resources/assets/js/components/GenerateApiToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { debounce } from '../lib/helper';

export default class GenerateApiToken {

constructor ($el) {
this.$el = $el;

this.$input = $el.querySelector('.api-token-input');
this.$btn = $el.querySelector('.api-token-generate');
this.$failureMsg = $el.querySelector('.api-token-generate-failure');

this.$btn.addEventListener('click', this.onButtonClick.bind(this));
}

onButtonClick () {
this.$btn.disabled = true;

this.fetchNewToken();
}

fetchNewToken () {

const fetchURL = window.appData.routes.ajax.generateApiToken;

fetch(fetchURL, {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
_token: window.appData.user.token
})
}).then((response) => {
return response.json();
}).then(response => {
this.handleResponse(response);
}).catch(() => {
this.showFailureMsg();
});

}

handleResponse (response) {

if (typeof response.new_token !== 'undefined') {
debounce(() => {
this.$input.value = response.new_token;
}, 1000);

window.setTimeout(() => {
this.$btn.disabled = false;
}, 5000);
} else {
this.showFailureMsg();
}

}

showFailureMsg () {
this.$failureMsg.classList.remove('d-none');
}
}
72 changes: 72 additions & 0 deletions resources/assets/js/components/GenerateCronToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { debounce } from '../lib/helper';

export default class GenerateCronToken {

constructor ($el) {
this.$el = $el;

this.$input = $el.querySelector('.cron-token-input');
this.$btn = $el.querySelector('.cron-token-generate');
this.$failureMsg = $el.querySelector('.cron-token-generate-failure');
this.$cronUrl = $el.querySelector('.cron-token-url');

this.$btn.addEventListener('click', this.onButtonClick.bind(this));
}

onButtonClick () {
this.$btn.disabled = true;

this.fetchNewToken();
}

fetchNewToken () {

if (!confirm(this.$el.dataset.confirmMessage)) {
this.$btn.disabled = false;
return;
}

const fetchURL = window.appData.routes.ajax.generateCronToken;

fetch(fetchURL, {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
_token: window.appData.user.token
})
}).then(response => {
return response.json();
}).then(response => {
this.handleResponse(response);
}).catch(() => {
this.showFailureMsg();
});

}

handleResponse (response) {

if (typeof response.new_token !== 'undefined') {
debounce(() => {
this.$input.value = response.new_token;
this.$cronUrl.innerHTML = this.buildCronURl(response.new_token);
}, 1000);

window.setTimeout(() => {
this.$btn.disabled = false;
}, 5000);
} else {
this.showFailureMsg();
}

}

showFailureMsg () {
this.$failureMsg.classList.remove('d-none');
}

buildCronURl (newToken) {
return this.$el.dataset.cronUrlBase + newToken;
}
}
11 changes: 6 additions & 5 deletions resources/assets/js/components/LoadingButton.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
export default class LoadingButton {

constructor ($el) {
this.$el = $el;
this.$form = this.$el.form;
this.$btn = $el;
this.$form = this.$btn.form;

this.$el.addEventListener('click', this.onClick.bind(this))
this.$btn.addEventListener('click', this.onClick.bind(this));
}

onClick () {
if (this.formIsValid()) {
this.$el.disabled = true;
this.$btn.disabled = true;
this.$form.submit();
}
}

formIsValid() {
formIsValid () {
return this.$form.checkValidity();
}
}
15 changes: 15 additions & 0 deletions resources/assets/js/components/ShareToggleAll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default class ShareToggleAll {

constructor ($el) {
this.$toggle = $el;
this.shareToggles = document.documentElement.querySelectorAll('.sharing-checkbox-input');

this.$toggle.addEventListener('click', this.onToggleClick.bind(this));
}

onToggleClick () {
this.shareToggles.forEach(toggle => {
toggle.checked = !toggle.checked;
})
}
}
8 changes: 8 additions & 0 deletions resources/assets/js/components/SimpleSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default class SimpleSelect {

constructor ($el) {
$($el).selectize({
create: false
});
}
}
Loading

0 comments on commit af5f64e

Please sign in to comment.