mirror of
https://github.com/wallabag/wallabag.git
synced 2026-03-19 18:27:42 +01:00
Compare commits
32 Commits
d1bd8ba628
...
6fddb902e8
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fddb902e8 | |||
| 387224f830 | |||
| e4fb100163 | |||
| e28e1bddb4 | |||
| b266d6ca2f | |||
| 8b0e6319e4 | |||
| 7eaaf5d38c | |||
| a8cb9f4f77 | |||
| c9cfae11f7 | |||
| 2054be7bd4 | |||
| f7c8466231 | |||
| e438b5e63f | |||
| 1fd861078d | |||
| a06da68e72 | |||
| 9da9e6b004 | |||
| ffeca7f94d | |||
| d515e11fe4 | |||
| 019d252446 | |||
| 503b82ea13 | |||
| 3125eb43ad | |||
| e5042074a2 | |||
| 76c101938d | |||
| a7a4c5fefb | |||
| 1b683dbb05 | |||
| a69ea46945 | |||
| 2a2172037e | |||
| 0589066ed1 | |||
| 1f76184d02 | |||
| 66c6a25941 | |||
| 46f505f69f | |||
| b4483023e6 | |||
| 31e1be4191 |
@ -52,6 +52,9 @@ fix-cs: ## Run PHP-CS-Fixer
|
||||
phpstan: ## Run PHPStan
|
||||
@$(PHP_NO_XDEBUG) bin/phpstan analyse
|
||||
|
||||
phpstan-baseline: ## Generate PHPStan baseline
|
||||
@$(PHP_NO_XDEBUG) bin/phpstan analyse --generate-baseline
|
||||
|
||||
lint-js: ## Run ESLint
|
||||
@$(YARN) lint:js
|
||||
|
||||
|
||||
11
assets/bootstrap.js
vendored
Normal file
11
assets/bootstrap.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { startStimulusApp } from '@symfony/stimulus-bridge';
|
||||
|
||||
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
|
||||
export default startStimulusApp(require.context(
|
||||
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
|
||||
true,
|
||||
/\.[jt]sx?$/,
|
||||
));
|
||||
|
||||
// register any custom, 3rd party controllers here
|
||||
// app.register('some_controller_name', SomeImportedController);
|
||||
4
assets/controllers.json
Normal file
4
assets/controllers.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"controllers": [],
|
||||
"entrypoints": []
|
||||
}
|
||||
13
assets/controllers/add_tag_controller.js
Normal file
13
assets/controllers/add_tag_controller.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['input'];
|
||||
|
||||
toggle() {
|
||||
this.element.classList.toggle('hidden');
|
||||
|
||||
if (!this.element.classList.contains('hidden')) {
|
||||
this.inputTarget.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
assets/controllers/annotations_controller.js
Normal file
57
assets/controllers/annotations_controller.js
Normal file
@ -0,0 +1,57 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import annotator from 'annotator';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
entryId: Number,
|
||||
createUrl: String,
|
||||
updateUrl: String,
|
||||
destroyUrl: String,
|
||||
searchUrl: String,
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.app = new annotator.App();
|
||||
|
||||
this.app.include(annotator.ui.main, {
|
||||
element: this.element,
|
||||
});
|
||||
|
||||
const authorization = {
|
||||
permits() { return true; },
|
||||
};
|
||||
this.app.registry.registerUtility(authorization, 'authorizationPolicy');
|
||||
|
||||
this.app.include(annotator.storage.http, {
|
||||
prefix: '',
|
||||
urls: {
|
||||
create: this.createUrlValue,
|
||||
update: this.updateUrlValue,
|
||||
destroy: this.destroyUrlValue,
|
||||
search: this.searchUrlValue,
|
||||
},
|
||||
entryId: this.entryIdValue,
|
||||
onError(msg, xhr) {
|
||||
if (!Object.prototype.hasOwnProperty.call(xhr, 'responseJSON')) {
|
||||
annotator.notification.banner('An error occurred', 'error');
|
||||
return;
|
||||
}
|
||||
Object.values(xhr.responseJSON.children).forEach((v) => {
|
||||
if (v.errors) {
|
||||
Object.values(v.errors).forEach((errorText) => {
|
||||
annotator.notification.banner(errorText, 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
this.app.start().then(() => {
|
||||
this.app.annotations.load({ entry: this.entryIdValue });
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.app.destroy();
|
||||
}
|
||||
}
|
||||
15
assets/controllers/batch_edit_controller.js
Normal file
15
assets/controllers/batch_edit_controller.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['item', 'tagAction'];
|
||||
|
||||
toggleSelection(e) {
|
||||
this.itemTargets.forEach((item) => {
|
||||
item.checked = e.currentTarget.checked; // eslint-disable-line no-param-reassign
|
||||
});
|
||||
}
|
||||
|
||||
tagSelection() {
|
||||
this.element.requestSubmit(this.tagActionTarget);
|
||||
}
|
||||
}
|
||||
16
assets/controllers/clipboard_controller.js
Normal file
16
assets/controllers/clipboard_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import ClipboardJS from 'clipboard';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.clipboard = new ClipboardJS(this.element);
|
||||
|
||||
this.clipboard.on('success', (e) => {
|
||||
e.clearSelection();
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.clipboard.destroy();
|
||||
}
|
||||
}
|
||||
16
assets/controllers/config_controller.js
Normal file
16
assets/controllers/config_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['previewArticle', 'previewContent', 'font', 'fontSize', 'lineHeight', 'maxWidth'];
|
||||
|
||||
connect() {
|
||||
this.updatePreview();
|
||||
}
|
||||
|
||||
updatePreview() {
|
||||
this.previewArticleTarget.style.maxWidth = `${this.maxWidthTarget.value}em`;
|
||||
this.previewContentTarget.style.fontFamily = this.fontTarget.value;
|
||||
this.previewContentTarget.style.fontSize = `${this.fontSizeTarget.value}em`;
|
||||
this.previewContentTarget.style.lineHeight = `${this.lineHeightTarget.value}em`;
|
||||
}
|
||||
}
|
||||
39
assets/controllers/dark_theme_controller.js
Normal file
39
assets/controllers/dark_theme_controller.js
Normal file
@ -0,0 +1,39 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.#choose();
|
||||
|
||||
this.mql = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
this.mql.addEventListener('change', this.#choose.bind(this));
|
||||
}
|
||||
|
||||
useLight() {
|
||||
this.element.classList.remove('dark-theme');
|
||||
document.cookie = 'theme=light;samesite=Lax;path=/;max-age=31536000';
|
||||
}
|
||||
|
||||
useDark() {
|
||||
this.element.classList.add('dark-theme');
|
||||
document.cookie = 'theme=dark;samesite=Lax;path=/;max-age=31536000';
|
||||
}
|
||||
|
||||
useAuto() {
|
||||
document.cookie = 'theme=auto;samesite=Lax;path=/;max-age=0';
|
||||
this.#choose();
|
||||
}
|
||||
|
||||
#choose() {
|
||||
const themeCookieExists = document.cookie.split(';').some((cookie) => cookie.trim().startsWith('theme='));
|
||||
|
||||
if (themeCookieExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mql.matches) {
|
||||
this.element.classList.add('dark-theme');
|
||||
} else {
|
||||
this.element.classList.remove('dark-theme');
|
||||
}
|
||||
}
|
||||
}
|
||||
58
assets/controllers/entries_navigation_controller.js
Normal file
58
assets/controllers/entries_navigation_controller.js
Normal file
@ -0,0 +1,58 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['card', 'paginationWrapper'];
|
||||
|
||||
connect() {
|
||||
this.pagination = this.paginationWrapperTarget.querySelector('.pagination');
|
||||
|
||||
this.cardIndex = 0;
|
||||
this.lastCardIndex = this.cardTargets.length - 1;
|
||||
|
||||
/* If we come from next page */
|
||||
if (window.location.hash === '#prev') {
|
||||
this.cardIndex = this.lastCardIndex;
|
||||
}
|
||||
|
||||
this.currentCard = this.cardTargets[this.cardIndex];
|
||||
|
||||
this.currentCard.classList.add('z-depth-4');
|
||||
}
|
||||
|
||||
selectRightCard() {
|
||||
if (this.cardIndex >= 0 && this.cardIndex < this.lastCardIndex) {
|
||||
this.currentCard.classList.remove('z-depth-4');
|
||||
this.cardIndex += 1;
|
||||
this.currentCard = this.cardTargets[this.cardIndex];
|
||||
this.currentCard.classList.add('z-depth-4');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pagination && this.pagination.querySelector('a[rel="next"]')) {
|
||||
window.location.href = this.pagination.querySelector('a[rel="next"]').href;
|
||||
}
|
||||
}
|
||||
|
||||
selectLeftCard() {
|
||||
if (this.cardIndex > 0 && this.cardIndex <= this.lastCardIndex) {
|
||||
this.currentCard.classList.remove('z-depth-4');
|
||||
this.cardIndex -= 1;
|
||||
this.currentCard = this.cardTargets[this.cardIndex];
|
||||
this.currentCard.classList.add('z-depth-4');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pagination && this.pagination.querySelector('a[rel="prev"]')) {
|
||||
window.location.href = `${this.pagination.querySelector('a[rel="prev"]').href}#prev`;
|
||||
}
|
||||
}
|
||||
|
||||
selectCurrentCard() {
|
||||
const url = this.currentCard.querySelector('a.card-title').href;
|
||||
if (url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
assets/controllers/fake_radio_controller.js
Normal file
13
assets/controllers/fake_radio_controller.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['emailTwoFactor', 'googleTwoFactor'];
|
||||
|
||||
uncheckGoogle() {
|
||||
this.googleTwoFactorTarget.checked = false;
|
||||
}
|
||||
|
||||
uncheckEmail() {
|
||||
this.emailTwoFactorTarget.checked = false;
|
||||
}
|
||||
}
|
||||
11
assets/controllers/highlight_controller.js
Normal file
11
assets/controllers/highlight_controller.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.querySelectorAll('pre code').forEach((element) => {
|
||||
hljs.highlightElement(element);
|
||||
});
|
||||
}
|
||||
}
|
||||
7
assets/controllers/leftbar_controller.js
Normal file
7
assets/controllers/leftbar_controller.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
toggleAddTagForm() {
|
||||
this.dispatch('toggleAddTagForm');
|
||||
}
|
||||
}
|
||||
16
assets/controllers/materialize/collapsible_controller.js
Normal file
16
assets/controllers/materialize/collapsible_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
accordion: { type: Boolean, default: true },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.instance = M.Collapsible.init(this.element, { accordion: this.accordionValue });
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
16
assets/controllers/materialize/dropdown_controller.js
Normal file
16
assets/controllers/materialize/dropdown_controller.js
Normal file
@ -0,0 +1,16 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.Dropdown.init(this.element, {
|
||||
hover: false,
|
||||
coverTrigger: false,
|
||||
constrainWidth: false,
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
32
assets/controllers/materialize/fab_controller.js
Normal file
32
assets/controllers/materialize/fab_controller.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
edge: { type: String, default: 'left' },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.instance = M.FloatingActionButton.init(this.element);
|
||||
}
|
||||
|
||||
autoDisplay() {
|
||||
const scrolled = (window.innerHeight + window.scrollY) >= document.body.offsetHeight;
|
||||
|
||||
if (scrolled) {
|
||||
this.toggleScroll = true;
|
||||
this.instance.open();
|
||||
} else if (this.toggleScroll === true) {
|
||||
this.toggleScroll = false;
|
||||
this.instance.close();
|
||||
}
|
||||
}
|
||||
|
||||
click() {
|
||||
this.dispatch('click');
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/form_select_controller.js
Normal file
12
assets/controllers/materialize/form_select_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.FormSelect.init(this.element.querySelector('select'));
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
24
assets/controllers/materialize/sidenav_controller.js
Normal file
24
assets/controllers/materialize/sidenav_controller.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
const mobileMaxWidth = 993;
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
edge: { type: String, default: 'left' },
|
||||
};
|
||||
|
||||
connect() {
|
||||
this.instance = M.Sidenav.init(this.element, { edge: this.edgeValue });
|
||||
}
|
||||
|
||||
close() {
|
||||
if (window.innerWidth < mobileMaxWidth) {
|
||||
this.instance.close();
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/tabs_controller.js
Normal file
12
assets/controllers/materialize/tabs_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.Tabs.init(this.element);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/toast_controller.js
Normal file
12
assets/controllers/materialize/toast_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.toast({ text: this.element.innerText });
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.dismissAll();
|
||||
}
|
||||
}
|
||||
12
assets/controllers/materialize/tooltip_controller.js
Normal file
12
assets/controllers/materialize/tooltip_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import M from '@materializecss/materialize';
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.instance = M.Tooltip.init(this.element);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
10
assets/controllers/qrcode_controller.js
Normal file
10
assets/controllers/qrcode_controller.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import jrQrcode from 'jr-qrcode';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { url: String };
|
||||
|
||||
connect() {
|
||||
this.element.setAttribute('src', jrQrcode.getQrBase64(this.urlValue));
|
||||
}
|
||||
}
|
||||
10
assets/controllers/scroll_indicator_controller.js
Normal file
10
assets/controllers/scroll_indicator_controller.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
updateWidth() {
|
||||
const referenceHeight = document.body.offsetHeight - window.innerHeight;
|
||||
const scrollPercent = (window.scrollY / referenceHeight) * 100;
|
||||
|
||||
this.element.style.width = `${scrollPercent}%`;
|
||||
}
|
||||
}
|
||||
19
assets/controllers/scroll_storage_controller.js
Normal file
19
assets/controllers/scroll_storage_controller.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static values = { entryId: Number };
|
||||
|
||||
connect() {
|
||||
window.scrollTo({
|
||||
top: window.innerHeight * localStorage[`wallabag.article.${this.entryIdValue}.percent`],
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
saveScroll() {
|
||||
const scrollPercent = window.scrollY / window.innerHeight;
|
||||
const scrollPercentRounded = Math.round(scrollPercent * 100) / 100;
|
||||
|
||||
localStorage[`wallabag.article.${this.entryIdValue}.percent`] = scrollPercentRounded;
|
||||
}
|
||||
}
|
||||
141
assets/controllers/shortcuts_controller.js
Normal file
141
assets/controllers/shortcuts_controller.js
Normal file
@ -0,0 +1,141 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
import Mousetrap from 'mousetrap';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['openOriginal', 'markAsFavorite', 'markAsRead', 'deleteEntry', 'showAddUrl', 'showSearch', 'showActions'];
|
||||
|
||||
static outlets = ['entries-navigation'];
|
||||
|
||||
connect() {
|
||||
/* Go to */
|
||||
Mousetrap.bind('g u', () => {
|
||||
window.location.href = Routing.generate('homepage');
|
||||
});
|
||||
Mousetrap.bind('g s', () => {
|
||||
window.location.href = Routing.generate('starred');
|
||||
});
|
||||
Mousetrap.bind('g r', () => {
|
||||
window.location.href = Routing.generate('archive');
|
||||
});
|
||||
Mousetrap.bind('g a', () => {
|
||||
window.location.href = Routing.generate('all');
|
||||
});
|
||||
Mousetrap.bind('g t', () => {
|
||||
window.location.href = Routing.generate('tag');
|
||||
});
|
||||
Mousetrap.bind('g c', () => {
|
||||
window.location.href = Routing.generate('config');
|
||||
});
|
||||
Mousetrap.bind('g i', () => {
|
||||
window.location.href = Routing.generate('import');
|
||||
});
|
||||
Mousetrap.bind('g d', () => {
|
||||
window.location.href = Routing.generate('developer');
|
||||
});
|
||||
Mousetrap.bind('?', () => {
|
||||
window.location.href = Routing.generate('howto');
|
||||
});
|
||||
Mousetrap.bind('g l', () => {
|
||||
window.location.href = Routing.generate('fos_user_security_logout');
|
||||
});
|
||||
|
||||
/* open original article */
|
||||
Mousetrap.bind('o', () => {
|
||||
if (!this.hasOpenOriginalTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openOriginalTarget.click();
|
||||
});
|
||||
|
||||
/* mark as favorite */
|
||||
Mousetrap.bind('f', () => {
|
||||
if (!this.hasMarkAsFavoriteTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markAsFavoriteTarget.click();
|
||||
});
|
||||
|
||||
/* mark as read */
|
||||
Mousetrap.bind('a', () => {
|
||||
if (!this.hasMarkAsReadTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markAsReadTarget.click();
|
||||
});
|
||||
|
||||
/* delete */
|
||||
Mousetrap.bind('del', () => {
|
||||
if (!this.hasDeleteEntryTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deleteEntryTarget.click();
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
Mousetrap.bind('g n', (e) => {
|
||||
if (!this.hasShowAddUrlTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.showAddUrlTarget.click();
|
||||
});
|
||||
|
||||
Mousetrap.bind('s', (e) => {
|
||||
if (!this.hasShowSearchTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.showSearchTarget.click();
|
||||
});
|
||||
|
||||
Mousetrap.bind('esc', (e) => {
|
||||
if (!this.hasShowActionsTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.showActionsTarget.click();
|
||||
});
|
||||
|
||||
const originalStopCallback = Mousetrap.prototype.stopCallback;
|
||||
|
||||
Mousetrap.prototype.stopCallback = (e, element, combo) => {
|
||||
// allow esc key to be used in input fields of topbar
|
||||
if (combo === 'esc' && element.dataset.topbarTarget !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return originalStopCallback(e, element);
|
||||
};
|
||||
|
||||
Mousetrap.bind('right', () => {
|
||||
if (!this.hasEntriesNavigationOutlet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entriesNavigationOutlet.selectRightCard();
|
||||
});
|
||||
|
||||
Mousetrap.bind('left', () => {
|
||||
if (!this.hasEntriesNavigationOutlet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entriesNavigationOutlet.selectLeftCard();
|
||||
});
|
||||
|
||||
Mousetrap.bind('enter', () => {
|
||||
if (!this.hasEntriesNavigationOutlet) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entriesNavigationOutlet.selectCurrentCard();
|
||||
});
|
||||
}
|
||||
}
|
||||
7
assets/controllers/sticky_nav_controller.js
Normal file
7
assets/controllers/sticky_nav_controller.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
toggle() {
|
||||
this.element.classList.toggle('entry-nav-top--sticky');
|
||||
}
|
||||
}
|
||||
12
assets/controllers/tag_controller.js
Normal file
12
assets/controllers/tag_controller.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['link', 'edit', 'form', 'input'];
|
||||
|
||||
showForm() {
|
||||
this.formTarget.classList.remove('hidden');
|
||||
this.editTarget.classList.add('hidden');
|
||||
this.linkTarget.classList.add('hidden');
|
||||
this.inputTarget.focus();
|
||||
}
|
||||
}
|
||||
31
assets/controllers/topbar_controller.js
Normal file
31
assets/controllers/topbar_controller.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { Controller } from '@hotwired/stimulus';
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ['addUrl', 'addUrlInput', 'search', 'searchInput', 'actions'];
|
||||
|
||||
showAddUrl() {
|
||||
this.actionsTarget.style.display = 'none';
|
||||
this.addUrlTarget.style.display = 'flex';
|
||||
this.searchTarget.style.display = 'none';
|
||||
this.addUrlInputTarget.focus();
|
||||
}
|
||||
|
||||
submittingUrl(e) {
|
||||
e.currentTarget.disabled = true;
|
||||
this.addUrlInputTarget.readOnly = true;
|
||||
this.addUrlInputTarget.blur();
|
||||
}
|
||||
|
||||
showSearch() {
|
||||
this.actionsTarget.style.display = 'none';
|
||||
this.addUrlTarget.style.display = 'none';
|
||||
this.searchTarget.style.display = 'flex';
|
||||
this.searchInputTarget.focus();
|
||||
}
|
||||
|
||||
showActions() {
|
||||
this.actionsTarget.style.display = 'flex';
|
||||
this.addUrlTarget.style.display = 'none';
|
||||
this.searchTarget.style.display = 'none';
|
||||
}
|
||||
}
|
||||
377
assets/index.js
377
assets/index.js
@ -1,18 +1,11 @@
|
||||
import $ from 'jquery';
|
||||
import './bootstrap';
|
||||
|
||||
/* Materialize imports */
|
||||
import '@materializecss/materialize/dist/css/materialize.css';
|
||||
import M from '@materializecss/materialize/dist/js/materialize';
|
||||
import '@materializecss/materialize/dist/js/materialize';
|
||||
|
||||
/* Annotations */
|
||||
import annotator from 'annotator';
|
||||
|
||||
import ClipboardJS from 'clipboard';
|
||||
import 'mathjax/es5/tex-svg';
|
||||
|
||||
/* jrQrcode */
|
||||
import jrQrcode from 'jr-qrcode';
|
||||
|
||||
/* Fonts */
|
||||
import 'material-design-icons-iconfont/dist/material-design-icons.css';
|
||||
import 'lato-font/css/lato-font.css';
|
||||
@ -22,371 +15,5 @@ import '@fontsource/eb-garamond';
|
||||
import '@fontsource/montserrat';
|
||||
import '@fontsource/oswald';
|
||||
|
||||
/* Highlight */
|
||||
import './js/highlight';
|
||||
|
||||
/* Tools */
|
||||
import {
|
||||
savePercent, retrievePercent, initPreviewText,
|
||||
} from './js/tools';
|
||||
|
||||
/* Import shortcuts */
|
||||
import './js/shortcuts/main';
|
||||
import './js/shortcuts/entry';
|
||||
|
||||
/* Theme style */
|
||||
import './scss/index.scss';
|
||||
|
||||
const mobileMaxWidth = 993;
|
||||
|
||||
/* ==========================================================================
|
||||
Annotations & Remember position
|
||||
========================================================================== */
|
||||
|
||||
$(document).ready(() => {
|
||||
if ($('#article').length) {
|
||||
const app = new annotator.App();
|
||||
|
||||
app.include(annotator.ui.main, {
|
||||
element: document.querySelector('article'),
|
||||
});
|
||||
|
||||
const authorization = {
|
||||
permits() { return true; },
|
||||
};
|
||||
app.registry.registerUtility(authorization, 'authorizationPolicy');
|
||||
|
||||
const x = JSON.parse($('#annotationroutes').html());
|
||||
app.include(annotator.storage.http, $.extend({}, x, {
|
||||
onError(msg, xhr) {
|
||||
if (!Object.prototype.hasOwnProperty.call(xhr, 'responseJSON')) {
|
||||
annotator.notification.banner('An error occurred', 'error');
|
||||
return;
|
||||
}
|
||||
$.each(xhr.responseJSON.children, (k, v) => {
|
||||
if (v.errors) {
|
||||
$.each(v.errors, (n, errorText) => {
|
||||
annotator.notification.banner(errorText, 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
app.start().then(() => {
|
||||
app.annotations.load({ entry: x.entryId });
|
||||
});
|
||||
|
||||
$(window).scroll(() => {
|
||||
const scrollTop = $(window).scrollTop();
|
||||
const docHeight = $(document).height();
|
||||
const scrollPercent = (scrollTop) / (docHeight);
|
||||
const scrollPercentRounded = Math.round(scrollPercent * 100) / 100;
|
||||
savePercent(x.entryId, scrollPercentRounded);
|
||||
});
|
||||
|
||||
retrievePercent(x.entryId);
|
||||
|
||||
$(window).resize(() => {
|
||||
retrievePercent(x.entryId, true);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-handler=tag-rename]').forEach((item) => {
|
||||
const current = item;
|
||||
current.wallabag_edit_mode = false;
|
||||
current.onclick = (event) => {
|
||||
const target = event.currentTarget;
|
||||
|
||||
if (target.wallabag_edit_mode === false) {
|
||||
$(target.parentNode.querySelector('[data-handle=tag-link]')).addClass('hidden');
|
||||
$(target.parentNode.querySelector('[data-handle=tag-rename-form]')).removeClass('hidden');
|
||||
target.parentNode.querySelector('[data-handle=tag-rename-form] input').focus();
|
||||
target.querySelector('.material-icons').innerHTML = 'done';
|
||||
|
||||
target.wallabag_edit_mode = true;
|
||||
} else {
|
||||
target.parentNode.querySelector('[data-handle=tag-rename-form]').submit();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// mimic radio button because emailTwoFactor is a boolean
|
||||
$('#update_user_googleTwoFactor').on('change', () => {
|
||||
$('#update_user_emailTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
$('#update_user_emailTwoFactor').on('change', () => {
|
||||
$('#update_user_googleTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
// same mimic for super admin
|
||||
$('#user_googleTwoFactor').on('change', () => {
|
||||
$('#user_emailTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
$('#user_emailTwoFactor').on('change', () => {
|
||||
$('#user_googleTwoFactor').prop('checked', false);
|
||||
});
|
||||
|
||||
// handle copy to clipboard for developer stuff
|
||||
const clipboard = new ClipboardJS('.btn');
|
||||
clipboard.on('success', (e) => {
|
||||
e.clearSelection();
|
||||
});
|
||||
});
|
||||
|
||||
(function darkTheme() {
|
||||
const rootEl = document.querySelector('html');
|
||||
const themeDom = {
|
||||
darkClass: 'dark-theme',
|
||||
|
||||
toggleClass(el) {
|
||||
return el.classList.toggle(this.darkClass);
|
||||
},
|
||||
|
||||
addClass(el) {
|
||||
return el.classList.add(this.darkClass);
|
||||
},
|
||||
|
||||
removeClass(el) {
|
||||
return el.classList.remove(this.darkClass);
|
||||
},
|
||||
};
|
||||
const themeCookie = {
|
||||
values: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
|
||||
name: 'theme',
|
||||
|
||||
getValue(isDarkTheme) {
|
||||
return isDarkTheme ? this.values.dark : this.values.light;
|
||||
},
|
||||
|
||||
setCookie(isDarkTheme) {
|
||||
const value = this.getValue(isDarkTheme);
|
||||
document.cookie = `${this.name}=${value};samesite=Lax;path=/;max-age=31536000`;
|
||||
},
|
||||
|
||||
removeCookie() {
|
||||
document.cookie = `${this.name}=auto;samesite=Lax;path=/;max-age=0`;
|
||||
},
|
||||
|
||||
exists() {
|
||||
return document.cookie.split(';').some((cookie) => cookie.trim().startsWith(`${this.name}=`));
|
||||
},
|
||||
};
|
||||
const preferedColorScheme = {
|
||||
choose() {
|
||||
const themeCookieExists = themeCookie.exists();
|
||||
if (this.isAvailable() && !themeCookieExists) {
|
||||
const isPreferedColorSchemeDark = window.matchMedia('(prefers-color-scheme: dark)').matches === true;
|
||||
if (!themeCookieExists) {
|
||||
themeDom[isPreferedColorSchemeDark ? 'addClass' : 'removeClass'](rootEl);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
isAvailable() {
|
||||
return typeof window.matchMedia === 'function';
|
||||
},
|
||||
|
||||
init() {
|
||||
if (!this.isAvailable()) {
|
||||
return false;
|
||||
}
|
||||
this.choose();
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addListener(() => {
|
||||
this.choose();
|
||||
});
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
const addDarkThemeListeners = () => {
|
||||
$(document).ready(() => {
|
||||
const lightThemeButtons = document.querySelectorAll('.js-theme-toggle[data-theme="light"]');
|
||||
[...lightThemeButtons].map((lightThemeButton) => {
|
||||
lightThemeButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
themeDom.removeClass(rootEl);
|
||||
themeCookie.setCookie(false);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
const darkThemeButtons = document.querySelectorAll('.js-theme-toggle[data-theme="dark"]');
|
||||
[...darkThemeButtons].map((darkThemeButton) => {
|
||||
darkThemeButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
themeDom.addClass(rootEl);
|
||||
themeCookie.setCookie(true);
|
||||
});
|
||||
return true;
|
||||
});
|
||||
const autoThemeButtons = document.querySelectorAll('.js-theme-toggle[data-theme="auto"]');
|
||||
[...autoThemeButtons].map((autoThemeButton) => {
|
||||
autoThemeButton.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
themeCookie.removeCookie();
|
||||
preferedColorScheme.choose();
|
||||
});
|
||||
return true;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
preferedColorScheme.init();
|
||||
addDarkThemeListeners();
|
||||
}());
|
||||
|
||||
const stickyNav = () => {
|
||||
const nav = $('.js-entry-nav-top');
|
||||
$('[data-toggle="actions"]').click(() => {
|
||||
nav.toggleClass('entry-nav-top--sticky');
|
||||
});
|
||||
};
|
||||
|
||||
const articleScroll = () => {
|
||||
const articleEl = $('#article');
|
||||
if (articleEl.length > 0) {
|
||||
$(window).scroll(() => {
|
||||
const s = $(window).scrollTop();
|
||||
const d = $(document).height();
|
||||
const c = $(window).height();
|
||||
const articleElBottom = articleEl.offset().top + articleEl.height();
|
||||
const scrollPercent = (s / (d - c)) * 100;
|
||||
$('.progress .determinate').css('width', `${scrollPercent}%`);
|
||||
const fixedActionBtn = $('.js-fixed-action-btn');
|
||||
const toggleScrollDataName = 'toggle-auto';
|
||||
if ((s + c) > articleElBottom) {
|
||||
fixedActionBtn.data(toggleScrollDataName, true);
|
||||
fixedActionBtn.floatingActionButton('open');
|
||||
} else if (fixedActionBtn.data(toggleScrollDataName) === true) {
|
||||
fixedActionBtn.data(toggleScrollDataName, false);
|
||||
fixedActionBtn.floatingActionButton('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
// sidenav
|
||||
document.querySelectorAll('.sidenav').forEach((element) => {
|
||||
$(element).sidenav({ edge: element.getAttribute('data-edge') ?? 'left' });
|
||||
});
|
||||
|
||||
$('select').formSelect();
|
||||
$('.collapsible[data-collapsible="accordion"]').collapsible();
|
||||
$('.collapsible[data-collapsible="expandable"]').collapsible({
|
||||
accordion: false,
|
||||
});
|
||||
|
||||
$('.dropdown-trigger').dropdown({ hover: false });
|
||||
$('.dropdown-trigger[data-covertrigger="false"][data-constrainwidth="false"]').dropdown({
|
||||
hover: false,
|
||||
coverTrigger: false,
|
||||
constrainWidth: false,
|
||||
});
|
||||
|
||||
$('.tabs').tabs();
|
||||
$('.tooltipped').tooltip();
|
||||
$('.fixed-action-btn').floatingActionButton();
|
||||
|
||||
stickyNav();
|
||||
articleScroll();
|
||||
initPreviewText();
|
||||
|
||||
const toggleNav = (toShow, toFocus) => {
|
||||
$('.nav-panel-actions').hide();
|
||||
$(toShow).show();
|
||||
$(toFocus).focus();
|
||||
};
|
||||
|
||||
$('#nav-btn-add-tag').on('click', () => {
|
||||
$('.nav-panel-add-tag').toggle();
|
||||
$('.nav-panel-menu').addClass('hidden');
|
||||
if (window.innerWidth < mobileMaxWidth) {
|
||||
$('.sidenav').sidenav('close');
|
||||
}
|
||||
$('#tag_label').focus();
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#nav-btn-add').on('click', () => {
|
||||
toggleNav('.nav-panel-add', '#entry_url');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('#config_fontsize').on('input', () => {
|
||||
const value = $('#config_fontsize').val();
|
||||
const css = `${value}em`;
|
||||
$('#preview-content').css('font-size', css);
|
||||
});
|
||||
|
||||
$('#config_font').on('change', () => {
|
||||
const value = $('#config_font').val();
|
||||
$('#preview-content').css('font-family', value);
|
||||
});
|
||||
|
||||
$('#config_lineHeight').on('input', () => {
|
||||
const value = $('#config_lineHeight').val();
|
||||
const css = `${value}em`;
|
||||
$('#preview-content').css('line-height', css);
|
||||
});
|
||||
|
||||
$('#config_maxWidth').on('input', () => {
|
||||
const value = $('#config_maxWidth').val();
|
||||
const css = `${value}em`;
|
||||
$('#preview-article').css('max-width', css);
|
||||
});
|
||||
|
||||
const materialAddForm = $('.nav-panel-add');
|
||||
materialAddForm.on('submit', () => {
|
||||
materialAddForm.addClass('disabled');
|
||||
$('input#entry_url', materialAddForm).prop('readonly', true).trigger('blur');
|
||||
});
|
||||
|
||||
$('#nav-btn-search').on('click', () => {
|
||||
toggleNav('.nav-panel-search', '#search_entry_term');
|
||||
return false;
|
||||
});
|
||||
|
||||
$('.close').on('click', (e) => {
|
||||
$(e.target).parent('.nav-panel-item').hide();
|
||||
$('.nav-panel-actions').show();
|
||||
return false;
|
||||
});
|
||||
|
||||
const mainCheckboxes = document.querySelectorAll('[data-js="checkboxes-toggle"]');
|
||||
if (mainCheckboxes.length) {
|
||||
[...mainCheckboxes].forEach((el) => {
|
||||
el.addEventListener('click', () => {
|
||||
const checkboxes = document.querySelectorAll(el.dataset.toggle);
|
||||
[...checkboxes].forEach((checkbox) => {
|
||||
const checkboxClone = checkbox;
|
||||
checkboxClone.checked = el.checked;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
$('form[name="form_mass_action"] input[name="tags"]').on('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
$('form[name="form_mass_action"] button[name="tag"]').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('img.jr-qrcode').forEach((qrcode) => {
|
||||
const src = jrQrcode.getQrBase64(qrcode.getAttribute('data-url'));
|
||||
|
||||
qrcode.setAttribute('src', src);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.material-toast').forEach((toast) => {
|
||||
M.toast({
|
||||
text: toast.innerText,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import 'highlight.js/styles/atom-one-light.css';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
document.querySelectorAll('pre').forEach((element) => {
|
||||
hljs.highlightElement(element);
|
||||
});
|
||||
});
|
||||
@ -1,26 +0,0 @@
|
||||
import Mousetrap from 'mousetrap';
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).ready(() => {
|
||||
if ($('#article').length > 0) {
|
||||
/* open original article */
|
||||
Mousetrap.bind('o', () => {
|
||||
$('ul.sidenav a.original i')[0].click();
|
||||
});
|
||||
|
||||
/* mark as favorite */
|
||||
Mousetrap.bind('f', () => {
|
||||
$('ul.sidenav a.favorite i')[0].click();
|
||||
});
|
||||
|
||||
/* mark as read */
|
||||
Mousetrap.bind('a', () => {
|
||||
$('ul.sidenav a.markasread i')[0].click();
|
||||
});
|
||||
|
||||
/* delete */
|
||||
Mousetrap.bind('del', () => {
|
||||
$('ul.sidenav a.delete i')[0].click();
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -1,104 +0,0 @@
|
||||
import Mousetrap from 'mousetrap';
|
||||
import $ from 'jquery';
|
||||
|
||||
/* Go to */
|
||||
Mousetrap.bind('g u', () => { window.location.href = Routing.generate('homepage'); });
|
||||
Mousetrap.bind('g s', () => { window.location.href = Routing.generate('starred'); });
|
||||
Mousetrap.bind('g r', () => { window.location.href = Routing.generate('archive'); });
|
||||
Mousetrap.bind('g a', () => { window.location.href = Routing.generate('all'); });
|
||||
Mousetrap.bind('g t', () => { window.location.href = Routing.generate('tag'); });
|
||||
Mousetrap.bind('g c', () => { window.location.href = Routing.generate('config'); });
|
||||
Mousetrap.bind('g i', () => { window.location.href = Routing.generate('import'); });
|
||||
Mousetrap.bind('g d', () => { window.location.href = Routing.generate('developer'); });
|
||||
Mousetrap.bind('?', () => { window.location.href = Routing.generate('howto'); });
|
||||
Mousetrap.bind('g l', () => { window.location.href = Routing.generate('fos_user_security_logout'); });
|
||||
|
||||
function toggleFocus(cardToToogleFocus) {
|
||||
if (cardToToogleFocus) {
|
||||
$(cardToToogleFocus).toggleClass('z-depth-4');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
const cards = $('#content').find('.card');
|
||||
const cardNumber = cards.length;
|
||||
let cardIndex = 0;
|
||||
/* If we come from next page */
|
||||
if (window.location.hash === '#prev') {
|
||||
cardIndex = cardNumber - 1;
|
||||
}
|
||||
let card = cards[cardIndex];
|
||||
const pagination = $('.pagination');
|
||||
|
||||
/* Show nothing on quickstart */
|
||||
if ($('#content > div.quickstart').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Show nothing on login/register page */
|
||||
if ($('#username').length > 0 || $('#fos_user_registration_form_username').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Show nothing on login/register page */
|
||||
if ($('#username').length > 0 || $('#fos_user_registration_form_username').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Focus current card */
|
||||
toggleFocus(card);
|
||||
|
||||
/* Actions */
|
||||
Mousetrap.bind('g n', () => {
|
||||
$('#nav-btn-add').trigger('click');
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('s', () => {
|
||||
$('#nav-btn-search').trigger('click');
|
||||
return false;
|
||||
});
|
||||
|
||||
Mousetrap.bind('esc', () => {
|
||||
$('.close').trigger('click');
|
||||
});
|
||||
|
||||
/* Select right card. If there's a next page, go to next page */
|
||||
Mousetrap.bind('right', () => {
|
||||
if (cardIndex >= 0 && cardIndex < cardNumber - 1) {
|
||||
toggleFocus(card);
|
||||
cardIndex += 1;
|
||||
card = cards[cardIndex];
|
||||
toggleFocus(card);
|
||||
return;
|
||||
}
|
||||
if (pagination.length > 0 && pagination.find('li.next:not(.disabled)').length > 0 && cardIndex === cardNumber - 1) {
|
||||
window.location.href = window.location.origin + $(pagination).find('li.next a').attr('href');
|
||||
}
|
||||
});
|
||||
|
||||
/* Select previous card. If there's a previous page, go to next page */
|
||||
Mousetrap.bind('left', () => {
|
||||
if (cardIndex > 0 && cardIndex < cardNumber) {
|
||||
toggleFocus(card);
|
||||
cardIndex -= 1;
|
||||
card = cards[cardIndex];
|
||||
toggleFocus(card);
|
||||
return;
|
||||
}
|
||||
if (pagination.length > 0 && $(pagination).find('li.prev:not(.disabled)').length > 0 && cardIndex === 0) {
|
||||
window.location.href = `${window.location.origin + $(pagination).find('li.prev a').attr('href')}#prev`;
|
||||
}
|
||||
});
|
||||
|
||||
Mousetrap.bind('enter', () => {
|
||||
if (typeof card !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = $(card).find('.card-title a').attr('href');
|
||||
if (typeof url === 'string' && url.length > 0) {
|
||||
window.location.href = window.location.origin + url;
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -1,54 +0,0 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
function supportsLocalStorage() {
|
||||
try {
|
||||
return 'localStorage' in window && window.localStorage !== null;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function savePercent(id, percent) {
|
||||
if (!supportsLocalStorage()) { return false; }
|
||||
localStorage[`wallabag.article.${id}.percent`] = percent;
|
||||
return true;
|
||||
}
|
||||
|
||||
function retrievePercent(id, resized) {
|
||||
if (!supportsLocalStorage()) { return false; }
|
||||
|
||||
const bheight = $(document).height();
|
||||
const percent = localStorage[`wallabag.article.${id}.percent`];
|
||||
const scroll = bheight * percent;
|
||||
|
||||
if (!resized) {
|
||||
window.scrollTo({
|
||||
top: scroll,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function initPreviewText() {
|
||||
// no display if preview_text not available
|
||||
if ($('div').is('#preview-article')) {
|
||||
const defaultFontFamily = $('#config_font').val();
|
||||
const defaultFontSize = $('#config_fontsize').val();
|
||||
const defaultLineHeight = $('#config_lineHeight').val();
|
||||
const defaultMaxWidth = $('#config_maxWidth').val();
|
||||
const previewContent = $('#preview-content');
|
||||
|
||||
previewContent.css('font-family', defaultFontFamily);
|
||||
previewContent.css('font-size', `${defaultFontSize}em`);
|
||||
previewContent.css('line-height', `${defaultLineHeight}em`);
|
||||
$('#preview-article').css('max-width', `${defaultMaxWidth}em`);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
savePercent,
|
||||
retrievePercent,
|
||||
initPreviewText,
|
||||
};
|
||||
@ -186,6 +186,14 @@ a.original:not(.waves-effect) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.card-tag-labels button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-tag-link {
|
||||
width: calc(100% - 24px);
|
||||
line-height: 1.3;
|
||||
@ -196,6 +204,7 @@ a.original:not(.waves-effect) {
|
||||
|
||||
.card-tag-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 100px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@ -80,14 +80,14 @@
|
||||
"@fontsource/eb-garamond": "^5.2.5",
|
||||
"@fontsource/montserrat": "^5.2.5",
|
||||
"@fontsource/oswald": "^5.2.5",
|
||||
"@hotwired/stimulus": "^3.2.2",
|
||||
"@materializecss/materialize": "^1.2.2",
|
||||
"@symfony/stimulus-bridge": "^4.0.0",
|
||||
"annotator": "wallabag/annotator#master",
|
||||
"clipboard": "^2.0.11",
|
||||
"hammerjs": "^2.0.8",
|
||||
"highlight.js": "^11.11.1",
|
||||
"icomoon-free-npm": "^0.0.0",
|
||||
"jquery": "^3.7.1",
|
||||
"jquery.cookie": "^1.4.1",
|
||||
"jr-qrcode": "^1.2.1",
|
||||
"material-design-icons-iconfont": "^6.7.0",
|
||||
"mathjax": "^3.2.2",
|
||||
@ -100,7 +100,7 @@
|
||||
"build:dev": "encore dev",
|
||||
"watch": "encore dev --watch",
|
||||
"build:prod": "encore production --progress",
|
||||
"lint:js": "eslint assets/*.js assets/js/*.js assets/js/**/*.js",
|
||||
"lint:js": "eslint assets/*.js assets/controllers/*.js",
|
||||
"lint:scss": "stylelint assets/scss/*.scss assets/scss/**/*.scss"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#"
|
||||
count: 1
|
||||
path: src/ParamConverter/UsernameFeedTokenConverter.php
|
||||
|
||||
@ -2,7 +2,7 @@ includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 3
|
||||
level: 4
|
||||
paths:
|
||||
- src
|
||||
- tests
|
||||
|
||||
@ -554,7 +554,7 @@ class EntryRestController extends WallabagRestController
|
||||
$this->entityManager->persist($entry);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
|
||||
$results[$key]['entry'] = $entry->getId();
|
||||
|
||||
// entry saved, dispatch event about it!
|
||||
$eventDispatcher->dispatch(new EntrySavedEvent($entry), EntrySavedEvent::NAME);
|
||||
@ -734,7 +734,7 @@ class EntryRestController extends WallabagRestController
|
||||
'html' => !empty($data['content']) ? $data['content'] : $entry->getContent(),
|
||||
'url' => $entry->getUrl(),
|
||||
'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(),
|
||||
'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(),
|
||||
'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt()?->format('Y-m-d H:i:s') ?? '',
|
||||
// faking the open graph preview picture
|
||||
'image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
|
||||
'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
|
||||
|
||||
@ -125,10 +125,6 @@ class FeedController extends AbstractController
|
||||
$perPage = $user->getConfig()->getFeedLimit() ?: $this->getParameter('wallabag.feed_limit');
|
||||
$entries->setMaxPerPage($perPage);
|
||||
|
||||
if (null === $entries) {
|
||||
throw $this->createNotFoundException('No entries found?');
|
||||
}
|
||||
|
||||
try {
|
||||
$entries->setCurrentPage($page);
|
||||
} catch (OutOfRangeCurrentPageException) {
|
||||
|
||||
@ -61,10 +61,10 @@ class PocketController extends AbstractController
|
||||
return $this->redirect($this->generateUrl('import_pocket'));
|
||||
}
|
||||
|
||||
$form = $request->request->get('form');
|
||||
$form = $request->request->all('form');
|
||||
|
||||
$this->session->set('import.pocket.code', $requestToken);
|
||||
if (null !== $form && \array_key_exists('mark_as_read', $form)) {
|
||||
if (\array_key_exists('mark_as_read', $form)) {
|
||||
$this->session->set('mark_as_read', $form['mark_as_read']);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace Wallabag\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Hateoas\Configuration\Annotation as Hateoas;
|
||||
use JMS\Serializer\Annotation\Exclude;
|
||||
@ -233,11 +234,14 @@ class Entry
|
||||
#[Groups(['export_all'])]
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @var Collection<Tag>
|
||||
*/
|
||||
#[ORM\JoinTable(name: 'entry_tag')]
|
||||
#[ORM\JoinColumn(name: 'entry_id', referencedColumnName: 'id', onDelete: 'cascade')]
|
||||
#[ORM\InverseJoinColumn(name: 'tag_id', referencedColumnName: 'id', onDelete: 'cascade')]
|
||||
#[ORM\ManyToMany(targetEntity: Tag::class, inversedBy: 'entries', cascade: ['persist'])]
|
||||
private $tags;
|
||||
private Collection $tags;
|
||||
|
||||
/*
|
||||
* @param User $user
|
||||
@ -589,7 +593,7 @@ class Entry
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDomainName()
|
||||
{
|
||||
@ -597,7 +601,7 @@ class Entry
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $domainName
|
||||
* @param string|null $domainName
|
||||
*/
|
||||
public function setDomainName($domainName)
|
||||
{
|
||||
@ -605,7 +609,7 @@ class Entry
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection
|
||||
* @return Collection
|
||||
*/
|
||||
public function getTags()
|
||||
{
|
||||
@ -696,7 +700,7 @@ class Entry
|
||||
/**
|
||||
* Get previewPicture.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPreviewPicture()
|
||||
{
|
||||
@ -706,7 +710,7 @@ class Entry
|
||||
/**
|
||||
* Set language.
|
||||
*
|
||||
* @param string $language
|
||||
* @param string|null $language
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
@ -720,7 +724,7 @@ class Entry
|
||||
/**
|
||||
* Get language.
|
||||
*
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
@ -808,7 +812,7 @@ class Entry
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
* @return \DateTimeInterface|null
|
||||
*/
|
||||
public function getPublishedAt()
|
||||
{
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace Wallabag\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use JMS\Serializer\Annotation\ExclusionPolicy;
|
||||
@ -41,8 +42,11 @@ class Tag implements \Stringable
|
||||
#[Expose]
|
||||
private $slug;
|
||||
|
||||
/**
|
||||
* @var Collection<Entry>
|
||||
*/
|
||||
#[ORM\ManyToMany(targetEntity: Entry::class, mappedBy: 'tags', cascade: ['persist'])]
|
||||
private $entries;
|
||||
private Collection $entries;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -121,7 +125,7 @@ class Tag implements \Stringable
|
||||
/**
|
||||
* Get entries for this tag.
|
||||
*
|
||||
* @return ArrayCollection<Entry>
|
||||
* @return Collection<Entry>
|
||||
*/
|
||||
public function getEntries()
|
||||
{
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace Wallabag\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use FOS\UserBundle\Model\User as BaseUser;
|
||||
use JMS\Serializer\Annotation\Accessor;
|
||||
@ -117,16 +118,16 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection&iterable<SiteCredential>
|
||||
* @var Collection<SiteCredential>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: SiteCredential::class, mappedBy: 'user', cascade: ['remove'])]
|
||||
protected $siteCredentials;
|
||||
protected Collection $siteCredentials;
|
||||
|
||||
/**
|
||||
* @var ArrayCollection&iterable<Client>
|
||||
* @var Collection<Client>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Client::class, mappedBy: 'user', cascade: ['remove'])]
|
||||
protected $clients;
|
||||
protected Collection $clients;
|
||||
|
||||
/**
|
||||
* @see getFirstClient() below
|
||||
@ -162,6 +163,8 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entries = new ArrayCollection();
|
||||
$this->siteCredentials = new ArrayCollection();
|
||||
$this->clients = new ArrayCollection();
|
||||
$this->roles = ['ROLE_USER'];
|
||||
}
|
||||
|
||||
@ -216,7 +219,7 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection<Entry>
|
||||
* @return Collection<Entry>
|
||||
*/
|
||||
public function getEntries()
|
||||
{
|
||||
@ -338,13 +341,15 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
*/
|
||||
public function addClient(Client $client)
|
||||
{
|
||||
$this->clients[] = $client;
|
||||
if (!$this->clients->contains($client)) {
|
||||
$this->clients->add($client);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection<Client>
|
||||
* @return Collection<Client>
|
||||
*/
|
||||
public function getClients()
|
||||
{
|
||||
@ -358,7 +363,7 @@ class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorI
|
||||
*/
|
||||
public function getFirstClient()
|
||||
{
|
||||
if (!empty($this->clients)) {
|
||||
if (!$this->clients->isEmpty()) {
|
||||
return $this->clients->first();
|
||||
}
|
||||
|
||||
|
||||
@ -79,7 +79,7 @@ class DownloadImagesSubscriber implements EventSubscriberInterface
|
||||
*
|
||||
* @todo If we want to add async download, it should be done in that method
|
||||
*
|
||||
* @return string|false False in case of async
|
||||
* @return string
|
||||
*/
|
||||
private function downloadImages(Entry $entry)
|
||||
{
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace Wallabag\Form\DataTransformer;
|
||||
|
||||
use Symfony\Component\Form\DataTransformerInterface;
|
||||
use Symfony\Component\Form\Exception\UnexpectedTypeException;
|
||||
|
||||
/**
|
||||
* Transforms a comma-separated list to a proper PHP array.
|
||||
@ -21,8 +22,6 @@ class StringToListTransformer implements DataTransformerInterface
|
||||
/**
|
||||
* Transforms a list to a string.
|
||||
*
|
||||
* @param array|null $list
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function transform($list)
|
||||
@ -31,14 +30,16 @@ class StringToListTransformer implements DataTransformerInterface
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!\is_array($list)) {
|
||||
throw new UnexpectedTypeException($list, 'array');
|
||||
}
|
||||
|
||||
return implode($this->separator, $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a string to a list.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function reverseTransform($string)
|
||||
@ -47,6 +48,10 @@ class StringToListTransformer implements DataTransformerInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\is_string($string)) {
|
||||
throw new UnexpectedTypeException($string, 'string');
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map('trim', explode($this->separator, $string))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,11 +143,8 @@ class ContentProxy
|
||||
}
|
||||
|
||||
try {
|
||||
// is it already a DateTime?
|
||||
// (it's inside the try/catch in case of fail to be parse time string)
|
||||
if (!$date instanceof \DateTime) {
|
||||
$date = new \DateTime($date);
|
||||
}
|
||||
$date = new \DateTime($date);
|
||||
|
||||
$entry->setPublishedAt($date);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@ -98,10 +98,10 @@ class DownloadImages
|
||||
* - re-saved it (for security reason)
|
||||
* - return the new local path.
|
||||
*
|
||||
* @param int $entryId ID of the entry
|
||||
* @param string $imagePath Path to the image to retrieve
|
||||
* @param string $url Url from where the image were found
|
||||
* @param string $relativePath Relative local path to saved the image
|
||||
* @param int $entryId ID of the entry
|
||||
* @param string|null $imagePath Path to the image to retrieve
|
||||
* @param string $url Url from where the image were found
|
||||
* @param string $relativePath Relative local path to saved the image
|
||||
*
|
||||
* @return string|false Relative url to access the image from the web
|
||||
*/
|
||||
@ -136,7 +136,7 @@ class DownloadImages
|
||||
}
|
||||
|
||||
$ext = $this->getExtensionFromResponse($res, $imagePath);
|
||||
if (false === $res) {
|
||||
if (false === $ext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -20,8 +20,8 @@ class Redirect
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url URL to redirect
|
||||
* @param bool $ignoreActionMarkAsRead Ignore configured action when mark as read
|
||||
* @param string|null $url URL to redirect
|
||||
* @param bool $ignoreActionMarkAsRead Ignore configured action when mark as read
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@ -129,7 +129,8 @@ class RuleBasedTagger
|
||||
private function fixEntry(Entry $entry)
|
||||
{
|
||||
$clonedEntry = clone $entry;
|
||||
$clonedEntry->setReadingTime($entry->getReadingTime() / $entry->getUser()->getConfig()->getReadingSpeed() * 200);
|
||||
$newReadingTime = (int) ($entry->getReadingTime() / $entry->getUser()->getConfig()->getReadingSpeed() * 200);
|
||||
$clonedEntry->setReadingTime($newReadingTime);
|
||||
|
||||
return $clonedEntry;
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ class PinboardImport extends AbstractImport
|
||||
'is_archived' => ('no' === $importedEntry['toread']) || $this->markAsRead,
|
||||
'is_starred' => false,
|
||||
'created_at' => $importedEntry['time'],
|
||||
'tags' => explode(' ', (string) $importedEntry['tags']),
|
||||
'tags' => array_filter(explode(' ', (string) $importedEntry['tags'])),
|
||||
];
|
||||
|
||||
$entry = new Entry($this->user);
|
||||
|
||||
@ -52,7 +52,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
|
||||
]);
|
||||
|
||||
$email = (new Email())
|
||||
->from(new Address($this->senderEmail, $this->senderName ?? $this->senderEmail))
|
||||
->from(new Address($this->senderEmail, $this->senderName ?: $this->senderEmail))
|
||||
->to($user->getEmailAuthRecipient())
|
||||
->subject($subject)
|
||||
->text($bodyText)
|
||||
|
||||
@ -24,10 +24,10 @@ class Utils
|
||||
*
|
||||
* @param string $text
|
||||
*
|
||||
* @return float
|
||||
* @return int
|
||||
*/
|
||||
public static function getReadingTime($text)
|
||||
{
|
||||
return floor(\count(preg_split('~([^\p{L}\p{N}\']+|(\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}){1,2})~u', strip_tags($text))) / 200);
|
||||
return (int) floor(\count(preg_split('~([^\p{L}\p{N}\']+|(\p{Han}|\p{Hiragana}|\p{Katakana}|\p{Hangul}){1,2})~u', strip_tags($text))) / 200);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="tabs-container col s12">
|
||||
<ul class="tabs">
|
||||
<ul class="tabs" data-controller="materialize--tabs">
|
||||
<li class="tab col s12 m6 l3"><a class="active" href="#set1">{{ 'config.tab_menu.settings'|trans }}</a></li>
|
||||
<li class="tab col s12 m6 l3"><a href="#set2">{{ 'config.tab_menu.feed'|trans }}</a></li>
|
||||
<li class="tab col s12 m6 l3"><a href="#set3">{{ 'config.tab_menu.user_info'|trans }}</a></li>
|
||||
@ -21,7 +21,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="set1" class="col s12">
|
||||
<div id="set1" class="col s12" data-controller="config">
|
||||
{{ form_start(form.config) }}
|
||||
{{ form_errors(form.config) }}
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
{{ form_label(form.config.items_per_page) }}
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_items_per_page'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_items_per_page'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
@ -47,7 +47,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_display_thumbnails'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_display_thumbnails'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
@ -64,14 +64,14 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_reading_speed'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_reading_speed'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s11">
|
||||
<div class="input-field col s11" data-controller="materialize--form-select">
|
||||
{{ form_errors(form.config.action_mark_as_read) }}
|
||||
{{ form_widget(form.config.action_mark_as_read) }}
|
||||
{{ form_label(form.config.action_mark_as_read) }}
|
||||
@ -79,13 +79,13 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s11">
|
||||
<div class="input-field col s11" data-controller="materialize--form-select">
|
||||
{{ form_errors(form.config.language) }}
|
||||
{{ form_widget(form.config.language) }}
|
||||
{{ form_label(form.config.language) }}
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_language'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_language'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
@ -102,7 +102,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_pocket_consumer_key'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_pocket_consumer_key'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
@ -112,31 +112,31 @@
|
||||
<div class="input-field col s12">
|
||||
<h5>{{ 'config.form_settings.android_configuration'|trans }}</h5>
|
||||
<a href="wallabag://{{ app.user.username }}@{{ wallabag_url }}" class="waves-effect waves-light btn hide-on-large-only">{{ 'config.form_settings.android_instruction'|trans }}</a>
|
||||
<img class="hide-on-med-and-down jr-qrcode" alt="{{ 'config.otp.app.qrcode_label'|trans }}" data-url="wallabag://{{ app.user.username }}@{{ wallabag_url }}" />
|
||||
<img data-controller="qrcode" data-qrcode-url-value="wallabag://{{ app.user.username }}@{{ wallabag_url }}" class="hide-on-med-and-down" alt="{{ 'config.otp.app.qrcode_label'|trans }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>{{ 'config.tab_menu.article_display'|trans }}</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s5">
|
||||
<div class="input-field col s5" data-controller="materialize--form-select">
|
||||
{{ form_errors(form.config.font) }}
|
||||
{{ form_widget(form.config.font) }}
|
||||
{{ form_widget(form.config.font, {attr: {'data-config-target': 'font', 'data-action': 'config#updatePreview'}}) }}
|
||||
{{ form_label(form.config.font) }}
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_font'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_font'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="input-field col s5">
|
||||
{{ form_errors(form.config.lineHeight) }}
|
||||
{{ form_widget(form.config.lineHeight) }}
|
||||
{{ form_widget(form.config.lineHeight, {attr: {'data-config-target': 'lineHeight', 'data-action': 'config#updatePreview'}}) }}
|
||||
{{ form_label(form.config.lineHeight, null, {'label_attr': {'class': 'settings-range-label'}}) }}
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_lineheight'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_lineheight'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
@ -145,22 +145,22 @@
|
||||
<div class="row">
|
||||
<div class="input-field col s5">
|
||||
{{ form_errors(form.config.fontsize) }}
|
||||
{{ form_widget(form.config.fontsize) }}
|
||||
{{ form_widget(form.config.fontsize, {attr: {'data-config-target': 'fontSize', 'data-action': 'config#updatePreview'}}) }}
|
||||
{{ form_label(form.config.fontsize, null, {'label_attr': {'class': 'settings-range-label'}}) }}
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_fontsize'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_fontsize'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="input-field col s5">
|
||||
{{ form_errors(form.config.maxWidth) }}
|
||||
{{ form_widget(form.config.maxWidth) }}
|
||||
{{ form_widget(form.config.maxWidth, {attr: {'data-config-target': 'maxWidth', 'data-action': 'config#updatePreview'}}) }}
|
||||
{{ form_label(form.config.maxWidth, null, {'label_attr': {'class': 'settings-range-label'}}) }}
|
||||
</div>
|
||||
<div class="input-field col s1">
|
||||
<a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_maxwidth'|trans }}">
|
||||
<a href="#" data-controller="materialize--tooltip" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_maxwidth'|trans }}">
|
||||
<i class="material-icons">live_help</i>
|
||||
</a>
|
||||
</div>
|
||||
@ -168,8 +168,8 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="input-field col s12">
|
||||
<div id="preview-article">
|
||||
<article id="preview-content">
|
||||
<div id="preview-article" data-config-target="previewArticle">
|
||||
<article id="preview-content" data-config-target="previewContent">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</article>
|
||||
</div>
|
||||
|
||||
@ -15,17 +15,17 @@
|
||||
<p>{{ 'config.otp.app.two_factor_code_description_2'|trans }}</p>
|
||||
|
||||
<p>
|
||||
<img class="hide-on-med-and-down jr-qrcode" data-url="{{ qr_code|raw }}" />
|
||||
<img data-controller="qrcode" data-qrcode-url-value="{{ qr_code|raw }}" class="hide-on-med-and-down" />
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{ 'config.otp.app.two_factor_code_description_5'|trans }} <pre>{{ secret }}</pre>
|
||||
</p>
|
||||
<div data-controller="highlight">
|
||||
{{ 'config.otp.app.two_factor_code_description_5'|trans }} <pre><code>{{ secret }}</code></pre>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<p>{{ 'config.otp.app.two_factor_code_description_3'|trans }}</p>
|
||||
|
||||
<p><pre>{{ backupCodes|join("\n") }}</pre></p>
|
||||
<div data-controller="highlight"><pre><code>{{ backupCodes|join("\n") }}</code></pre></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>{{ 'config.otp.app.two_factor_code_description_4'|trans }}</p>
|
||||
|
||||
@ -18,14 +18,14 @@
|
||||
<td>{{ 'developer.client_parameter.field_id'|trans }}</td>
|
||||
<td>
|
||||
<strong><code>{{ client_id }}</code></strong>
|
||||
<button class="btn" data-clipboard-text="{{ client_id }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
<button class="btn" data-controller="clipboard" data-clipboard-text="{{ client_id }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'developer.client_parameter.field_secret'|trans }}</td>
|
||||
<td>
|
||||
<strong><code>{{ client_secret }}</code></strong>
|
||||
<button class="btn" data-clipboard-text="{{ client_secret }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
<button class="btn" data-controller="clipboard" data-clipboard-text="{{ client_secret }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@ -12,16 +12,16 @@
|
||||
<p>{{ 'developer.howto.description.paragraph_2'|trans }}</p>
|
||||
<p>{{ 'developer.howto.description.paragraph_3'|trans({'%link%': path('developer_create_client')})|raw }}</p>
|
||||
<p>{{ 'developer.howto.description.paragraph_4'|trans }}</p>
|
||||
<p>
|
||||
<div data-controller="highlight">
|
||||
<pre><code class="language-bash">http POST {{ wallabag_url }}/oauth/v2/token \
|
||||
grant_type=password \
|
||||
client_id=12_5um6nz50ceg4088c0840wwc0kgg44g00kk84og044ggkscso0k \
|
||||
client_secret=3qd12zpeaxes8cwg8c0404g888co4wo8kc4gcw0occww8cgw4k \
|
||||
username=yourUsername \
|
||||
password=yourPassw0rd</code></pre>
|
||||
</p>
|
||||
</div>
|
||||
<p>{{ 'developer.howto.description.paragraph_5'|trans }}</p>
|
||||
<p>
|
||||
<div data-controller="highlight">
|
||||
<pre><code class="language-bash">HTTP/1.1 200 OK
|
||||
Cache-Control: no-store, private
|
||||
Connection: close
|
||||
@ -39,12 +39,12 @@ X-Powered-By: PHP/5.5.9-1ubuntu4.13
|
||||
"scope": null,
|
||||
"token_type": "bearer"
|
||||
}</code></pre>
|
||||
</p>
|
||||
</div>
|
||||
<p>{{ 'developer.howto.description.paragraph_6'|trans }}</p>
|
||||
<p>
|
||||
<div data-controller="highlight">
|
||||
<pre><code class="language-bash">http GET {{ wallabag_url }}/api/entries.json \
|
||||
"Authorization:Bearer ZWFjNjA3ZWMwYWVmYzRkYTBlMmQ3NTllYmVhOGJiZDE0ZTg1NjE4MjczOTVlNzM0ZTRlMWQ0MmRlMmYwNTk5Mw"</code></pre>
|
||||
</p>
|
||||
</div>
|
||||
<p>{{ 'developer.howto.description.paragraph_7'|trans }}</p>
|
||||
<p>{{ 'developer.howto.description.paragraph_8'|trans({'%link%': path('nelmio_api_doc.swagger_ui')})|raw }}</p>
|
||||
<p><a href="{{ path('developer') }}" class="waves-effect waves-light grey btn">{{ 'developer.howto.back'|trans }}</a></p>
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
<h4>{{ 'developer.existing_clients.title'|trans }}</h4>
|
||||
{% if clients %}
|
||||
<ul class="collapsible" data-collapsible="expandable">
|
||||
<ul class="collapsible" data-controller="materialize--collapsible" data-materialize--collapsible-accordion-value="false">
|
||||
{% for client in clients %}
|
||||
<li>
|
||||
<div class="collapsible-header">{{ client.name }} - #{{ client.id }}</div>
|
||||
@ -35,14 +35,14 @@
|
||||
<td>{{ 'developer.existing_clients.field_id'|trans }}</td>
|
||||
<td>
|
||||
<strong><code>{{ client.clientId }}</code></strong>
|
||||
<button class="btn" data-clipboard-text="{{ client.clientId }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
<button class="btn" data-controller="clipboard" data-clipboard-text="{{ client.clientId }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ 'developer.existing_clients.field_secret'|trans }}</td>
|
||||
<td>
|
||||
<strong><code>{{ client.secret }}</code></strong>
|
||||
<button class="btn" data-clipboard-text="{{ client.secret }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
<button class="btn" data-controller="clipboard" data-clipboard-text="{{ client.secret }}">{{ 'developer.client.copy_to_clipboard'|trans }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<label class="entry-checkbox">
|
||||
<input type="checkbox" class="entry-checkbox-input" data-js="entry-checkbox" name="entry-checkbox[]" value="{{ entry.id }}" />
|
||||
<input type="checkbox" class="entry-checkbox-input" name="entry-checkbox[]" value="{{ entry.id }}" data-batch-edit-target="item" />
|
||||
</label>
|
||||
|
||||
@ -24,21 +24,21 @@
|
||||
{% block nav_panel_extra_actions %}
|
||||
{% if active_route %}
|
||||
<li>
|
||||
<a class="waves-effect tooltipped js-random-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', {'type': active_route}) }}">
|
||||
<a class="waves-effect js-random-action" data-controller="materialize--tooltip" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', {'type': active_route}) }}">
|
||||
<i class="material-icons">casino</i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if has_filters %}
|
||||
<li class="button-filters">
|
||||
<a class="nav-panel-menu sidenav-trigger tooltipped js-filters-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.filter_entries'|trans }}" href="#" data-target="filters">
|
||||
<a class="nav-panel-menu sidenav-trigger js-filters-action" data-controller="materialize--tooltip" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.filter_entries'|trans }}" href="#" data-target="filters">
|
||||
<i class="material-icons">filter_list</i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if has_exports %}
|
||||
<li class="button-export">
|
||||
<a class="nav-panel-menu sidenav-trigger tooltipped js-export-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.export'|trans }}" href="#" data-target="export">
|
||||
<a class="nav-panel-menu sidenav-trigger js-export-action" data-controller="materialize--tooltip" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.export'|trans }}" href="#" data-target="export">
|
||||
<i class="material-icons">file_download</i>
|
||||
</a>
|
||||
</li>
|
||||
@ -53,15 +53,15 @@
|
||||
{% if current_route == 'homepage' %}
|
||||
{% set current_route = 'unread' %}
|
||||
{% endif %}
|
||||
<form name="form_mass_action" action="{{ path('mass_action', {redirect: current_path}) }}" method="post">
|
||||
<div class="results">
|
||||
<form name="form_mass_action" action="{{ path('mass_action', {redirect: current_path}) }}" method="post" data-controller="batch-edit entries-navigation">
|
||||
<div class="results" data-entries-navigation-target="paginationWrapper">
|
||||
<div class="nb-results">
|
||||
{{ 'entry.list.number_on_the_page'|trans({'%count%': entries.count}) }}
|
||||
{% if entries.count > 0 %}
|
||||
<a class="results-item" href="{{ path('switch_view_mode', {redirect: current_path}) }}"><i class="material-icons">{% if list_mode == 0 %}view_list{% else %}view_module{% endif %}</i></a>
|
||||
{% endif %}
|
||||
{% if entries.count > 0 and is_granted('EDIT_ENTRIES') %}
|
||||
<label for="mass-action-inputs-displayed" class="mass-action-toggle results-item tooltipped" data-position="right" data-delay="50" data-tooltip="{{ 'entry.list.toggle_mass_action'|trans }}"><i class="material-icons">library_add_check</i></label>
|
||||
<label for="mass-action-inputs-displayed" class="mass-action-toggle results-item" data-controller="materialize--tooltip" data-position="right" data-delay="50" data-tooltip="{{ 'entry.list.toggle_mass_action'|trans }}"><i class="material-icons">library_add_check</i></label>
|
||||
{% endif %}
|
||||
{% if app.user.config.feedToken %}
|
||||
{% include "Entry/_feed_link.html.twig" %}
|
||||
@ -77,22 +77,22 @@
|
||||
<input id="mass-action-inputs-displayed" class="toggle-checkbox" type="checkbox" />
|
||||
<div class="mass-action">
|
||||
<div class="mass-action-group">
|
||||
<input type="checkbox" class="entry-checkbox-input" data-toggle="[data-js='entry-checkbox']" data-js="checkboxes-toggle" />
|
||||
<input type="checkbox" class="entry-checkbox-input" data-action="batch-edit#toggleSelection" />
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" name="toggle-read" title="{{ 'entry.list.toogle_as_read'|trans }}"><i class="material-icons">done</i></button>
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" name="toggle-star" title="{{ 'entry.list.toogle_as_star'|trans }}" ><i class="material-icons">star</i></button>
|
||||
<button class="mass-action-button btn cyan darken-1" type="submit" name="delete" onclick="return confirm('{{ 'entry.confirm.delete_entries'|trans|escape('js') }}')" title="{{ 'entry.list.delete'|trans }}"><i class="material-icons">delete</i></button>
|
||||
</div>
|
||||
|
||||
<div class="mass-action-tags">
|
||||
<button class="btn cyan darken-1 mass-action-button mass-action-button--tags" type="submit" name="tag" title="{{ 'entry.list.add_tags'|trans }}"><i class="material-icons">label</i></button>
|
||||
<input type="text" class="mass-action-tags-input" name="tags" placeholder="{{ 'entry.list.mass_action_tags_input_placeholder'|trans }}" />
|
||||
<button class="btn cyan darken-1 mass-action-button mass-action-button--tags" type="submit" name="tag" title="{{ 'entry.list.add_tags'|trans }}" data-batch-edit-target="tagAction"><i class="material-icons">label</i></button>
|
||||
<input type="text" class="mass-action-tags-input" name="tags" placeholder="{{ 'entry.list.mass_action_tags_input_placeholder'|trans }}" data-action="keydown.enter->batch-edit#tagSelection:prevent:stop" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ol class="entries {% if list_mode == 1 %}collection{% else %}row entries-row data{% endif %}">
|
||||
|
||||
{% for entry in entries %}
|
||||
<li id="entry-{{ entry.id|e }}" class="{% if list_mode != 0 %}col collection-item{% endif %} s12" data-entry-id="{{ entry.id|e }}" data-test="entry">
|
||||
<li id="entry-{{ entry.id|e }}" class="{% if list_mode != 0 %}col collection-item{% endif %} s12" data-entry-id="{{ entry.id|e }}" data-test="entry" data-entries-navigation-target="card">
|
||||
{% if list_mode == 1 %}
|
||||
{% include "Entry/_card_list.html.twig" with {'entry': entry, 'currentRoute': current_route, 'routes': entries_with_archived_class_routes} only %}
|
||||
{% elseif not entry.previewPicture is null and entry.mimetype starts with 'image/' %}
|
||||
@ -114,7 +114,7 @@
|
||||
|
||||
<!-- Export -->
|
||||
{% if has_exports and is_granted('EXPORT_ENTRIES') %}
|
||||
<div id="export" class="sidenav" data-edge="right">
|
||||
<div id="export" class="sidenav" data-controller="materialize--sidenav" data-materialize--sidenav-edge-value="right">
|
||||
{% set current_tag = null %}
|
||||
{% if tag is defined %}
|
||||
{% set current_tag = tag.slug %}
|
||||
@ -140,7 +140,7 @@
|
||||
|
||||
<!-- Filters -->
|
||||
{% if has_filters %}
|
||||
<div id="filters" class="sidenav" data-edge="right">
|
||||
<div id="filters" class="sidenav" data-controller="materialize--sidenav" data-materialize--sidenav-edge-value="right">
|
||||
<form action="{{ path('all') }}">
|
||||
|
||||
<h4 class="center">{{ 'entry.filters.title'|trans }}</h4>
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
|
||||
{% block menu %}
|
||||
<div class="progress">
|
||||
<div class="determinate"></div>
|
||||
<div class="determinate" data-controller="scroll-indicator" data-action="scroll@window->scroll-indicator#updateWidth"></div>
|
||||
</div>
|
||||
<nav class="hide-on-large-only js-entry-nav-top">
|
||||
<nav class="hide-on-large-only" data-controller="sticky-nav" data-action="materialize--fab:click@window->sticky-nav#toggle">
|
||||
<div class="nav-panel-item cyan darken-1">
|
||||
<ul>
|
||||
<li>
|
||||
@ -42,7 +42,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<ul id="slide-out" class="left-bar collapsible sidenav sidenav-fixed reader-mode" data-collapsible="accordion">
|
||||
<ul id="slide-out" class="left-bar collapsible sidenav sidenav-fixed reader-mode" data-controller="materialize--sidenav materialize--collapsible leftbar">
|
||||
<li class="bold border-bottom hide-on-med-and-down">
|
||||
<a class="waves-effect collapsible-header" href="{{ path('homepage') }}">
|
||||
<i class="material-icons small">arrow_back</i>
|
||||
@ -52,7 +52,7 @@
|
||||
</li>
|
||||
|
||||
<li class="bold border-bottom hide-on-med-and-down">
|
||||
<a class="waves-effect collapsible-header original" href="{{ entry.url|e }}" target="_blank" rel="noopener">
|
||||
<a class="waves-effect collapsible-header original" href="{{ entry.url|e }}" target="_blank" rel="noopener" data-shortcuts-target="openOriginal">
|
||||
<i class="material-icons small">link</i>
|
||||
<span>{{ 'entry.view.left_menu.view_original_article'|trans }}</span>
|
||||
</a>
|
||||
@ -76,7 +76,7 @@
|
||||
|
||||
{% if is_granted('ARCHIVE', entry) %}
|
||||
<li class="bold hide-on-med-and-down">
|
||||
<a class="waves-effect collapsible-header markasread" title="{{ mark_as_read_label|trans }}" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" id="markAsRead">
|
||||
<a class="waves-effect collapsible-header markasread" title="{{ mark_as_read_label|trans }}" href="{{ path('archive_entry', {'id': entry.id, redirect: current_path}) }}" id="markAsRead" data-shortcuts-target="markAsRead">
|
||||
<i class="material-icons small">{% if entry.isArchived == 0 %}done{% else %}unarchive{% endif %}</i>
|
||||
<span>{{ mark_as_read_label|trans }}</span>
|
||||
</a>
|
||||
@ -86,7 +86,7 @@
|
||||
|
||||
{% if is_granted('STAR', entry) %}
|
||||
<li class="bold hide-on-med-and-down">
|
||||
<a class="waves-effect collapsible-header favorite" title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" id="setFav">
|
||||
<a class="waves-effect collapsible-header favorite" title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" href="{{ path('star_entry', {'id': entry.id, redirect: current_path}) }}" id="setFav" data-shortcuts-target="markAsFavorite">
|
||||
<i class="material-icons small">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
|
||||
<span>{{ 'entry.view.left_menu.set_as_starred'|trans }}</span>
|
||||
</a>
|
||||
@ -96,7 +96,7 @@
|
||||
|
||||
{% if is_granted('DELETE', entry) %}
|
||||
<li class="bold border-bottom">
|
||||
<a class="waves-effect collapsible-header delete" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.delete'|trans }}" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}">
|
||||
<a class="waves-effect collapsible-header delete" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.delete'|trans }}" href="{{ path('delete_entry', {'id': entry.id, redirect: current_path}) }}" data-shortcuts-target="deleteEntry">
|
||||
<i class="material-icons small">delete</i>
|
||||
<span>{{ 'entry.view.left_menu.delete'|trans }}</span>
|
||||
</a>
|
||||
@ -105,7 +105,7 @@
|
||||
{% endif %}
|
||||
|
||||
<li class="bold border-bottom">
|
||||
<a class="waves-effect collapsible-header" id="nav-btn-add-tag">
|
||||
<a class="waves-effect collapsible-header" id="nav-btn-add-tag" data-action="click->materialize--sidenav#close click->leftbar#toggleAddTagForm">
|
||||
<i class="material-icons small">label_outline</i>
|
||||
<span>{{ 'entry.view.left_menu.add_a_tag'|trans }}</span>
|
||||
</a>
|
||||
@ -119,19 +119,19 @@
|
||||
</a>
|
||||
<ul class="collapsible-body">
|
||||
<li>
|
||||
<a href="#" class="js-theme-toggle" data-theme="light">
|
||||
<a href="#" data-action="click->dark-theme#useLight:prevent">
|
||||
<i class="theme-toggle-icon material-icons tiny">brightness_high</i>
|
||||
<span>{{ 'entry.view.left_menu.theme_toggle_light'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="js-theme-toggle" data-theme="dark">
|
||||
<a href="#" data-action="click->dark-theme#useDark:prevent">
|
||||
<i class="theme-toggle-icon material-icons tiny">brightness_low</i>
|
||||
<span>{{ 'entry.view.left_menu.theme_toggle_dark'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="js-theme-toggle" data-theme="auto">
|
||||
<a href="#" data-action="click->dark-theme#useAuto:prevent">
|
||||
<i class="theme-toggle-icon material-icons tiny">brightness_auto</i>
|
||||
<span>{{ 'entry.view.left_menu.theme_toggle_auto'|trans }}</span>
|
||||
</a>
|
||||
@ -262,7 +262,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="article" class="article">
|
||||
<div id="article" class="article" data-controller="scroll-storage" data-scroll-storage-entry-id-value="{{ entry.id }}" data-action="scroll@window->scroll-storage#saveScroll">
|
||||
<header class="mbm">
|
||||
<h1>
|
||||
<span{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>{{ entry.title|striptags|default('entry.default_title'|trans)|raw }}</span>
|
||||
@ -320,17 +320,25 @@
|
||||
</div>
|
||||
|
||||
{% if is_granted('TAG', entry) %}
|
||||
<div class="input-field nav-panel-add-tag" style="display: none">
|
||||
<div class="input-field nav-panel-add-tag hidden" data-controller="add-tag" data-action="leftbar:toggleAddTagForm@window->add-tag#toggle">
|
||||
{{ render(controller('Wallabag\\Controller\\TagController::addTagFormAction', {'id': entry.id})) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</aside>
|
||||
<article{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}>
|
||||
<article
|
||||
{% if entry.language is defined and entry.language is not null %} lang="{{ entry.getHTMLLanguage() }}"{% endif %}
|
||||
data-controller="highlight annotations"
|
||||
data-annotations-entry-id-value="{{ entry.id }}"
|
||||
data-annotations-create-url-value="{{ path('annotations_post_annotation', {'entry': entry.id}) }}"
|
||||
data-annotations-update-url-value="{{ path('annotations_put_annotation', {'annotation': 'idAnnotation'}) }}"
|
||||
data-annotations-destroy-url-value="{{ path('annotations_delete_annotation', {'annotation': 'idAnnotation'}) }}"
|
||||
data-annotations-search-url-value="{{ path('annotations_get_annotations', {'entry': entry.id}) }}"
|
||||
>
|
||||
{{ entry.content|raw }}
|
||||
</article>
|
||||
|
||||
<div class="fixed-action-btn js-fixed-action-btn horizontal click-to-toggle hide-on-large-only">
|
||||
<div class="fixed-action-btn horizontal click-to-toggle hide-on-large-only" data-controller="materialize--fab" data-action="scroll@window->materialize--fab#autoDisplay click->materialize--fab#click">
|
||||
<a class="btn-floating btn-large" data-toggle="actions">
|
||||
<i class="material-icons">menu</i>
|
||||
</a>
|
||||
@ -347,19 +355,6 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="annotationroutes" type="application/json">
|
||||
{
|
||||
"prefix": "",
|
||||
"urls": {
|
||||
"create": "{{ path('annotations_post_annotation', {'entry': entry.id}) }}",
|
||||
"update": "{{ path('annotations_put_annotation', {'annotation': 'idAnnotation'}) }}",
|
||||
"destroy": "{{ path('annotations_delete_annotation', {'annotation': 'idAnnotation'}) }}",
|
||||
"search": "{{ path('annotations_get_annotations', {'entry': entry.id}) }}"
|
||||
},
|
||||
"entryId": "{{ entry.id }}"
|
||||
}</script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<form class="input-field nav-panel-item nav-panel-add" style="display: none" name="entry" method="post" action="{{ path('new_entry') }}">
|
||||
<form class="input-field nav-panel-item nav-panel-add" style="display: none" name="entry" method="post" action="{{ path('new_entry') }}" data-topbar-target="addUrl" data-action="topbar#submittingUrl">
|
||||
{% if form_errors(form) %}
|
||||
<span class="black-text">{{ form_errors(form) }}</span>
|
||||
{% endif %}
|
||||
@ -8,8 +8,8 @@
|
||||
<span class="black-text">{{ form_errors(form.url) }}</span>
|
||||
{% endif %}
|
||||
|
||||
{{ form_widget(form.url, {'attr': {'autocomplete': 'off', 'placeholder': 'entry.new.placeholder'}}) }}
|
||||
<i class="material-icons close" aria-label="clear" role="button"></i>
|
||||
{{ form_widget(form.url, {'attr': {'autocomplete': 'off', 'placeholder': 'entry.new.placeholder', 'data-topbar-target': 'addUrlInput'}}) }}
|
||||
<i class="material-icons close" aria-label="clear" role="button" data-action="click->topbar#showActions" data-shortcuts-target="showActions"></i>
|
||||
|
||||
{{ form_rest(form) }}
|
||||
</form>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<form class="input-field nav-panel-item nav-panel-search" style="display: none" name="search" method="GET" action="{{ path('search') }}">
|
||||
<form class="input-field nav-panel-item nav-panel-search" style="display: none" name="search" method="GET" action="{{ path('search') }}" data-topbar-target="search">
|
||||
{% if form_errors(form) %}
|
||||
<span class="black-text">{{ form_errors(form) }}</span>
|
||||
{% endif %}
|
||||
@ -10,8 +10,8 @@
|
||||
|
||||
<input type="hidden" name="currentRoute" value="{{ currentRoute }}" />
|
||||
|
||||
{{ form_widget(form.term, {'attr': {'autocomplete': 'off', 'placeholder': 'entry.search.placeholder'}}) }}
|
||||
<i class="material-icons close" aria-label="clear" role="button"></i>
|
||||
{{ form_widget(form.term, {'attr': {'autocomplete': 'off', 'placeholder': 'entry.search.placeholder', 'data-topbar-target': 'searchInput'}}) }}
|
||||
<i class="material-icons close" aria-label="clear" role="button" data-action="click->topbar#showActions" data-shortcuts-target="showActions"></i>
|
||||
|
||||
{{ form_rest(form) }}
|
||||
</form>
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{% if nbRedisMessages is defined and nbRedisMessages > 0 %}
|
||||
<p class="material-toast hidden">Messages in queue: {{ nbRedisMessages }}</p>
|
||||
<p class="hidden" data-controller="materialize--toast">Messages in queue: {{ nbRedisMessages }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if nbRabbitMessages is defined and nbRabbitMessages > 0 %}
|
||||
<p class="material-toast hidden">Messages in queue: {{ nbRabbitMessages }}</p>
|
||||
<p class="hidden" data-controller="materialize--toast">Messages in queue: {{ nbRabbitMessages }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if redisNotInstalled is defined and redisNotInstalled %}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="tabs-container col s12">
|
||||
<ul class="tabs">
|
||||
<ul class="tabs" data-controller="materialize--tabs">
|
||||
<li class="tab col s12 m6 l3"><a class="active" href="#set1">{{ 'about.top_menu.who_behind_wallabag'|trans }}</a></li>
|
||||
<li class="tab col s12 m6 l3"><a href="#set2">{{ 'about.top_menu.getting_help'|trans }}</a></li>
|
||||
<li class="tab col s12 m6 l3"><a href="#set3">{{ 'about.top_menu.helping'|trans }}</a></li>
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="tabs-container col s12">
|
||||
<ul class="tabs">
|
||||
<ul class="tabs" data-controller="materialize--tabs">
|
||||
<li class="tab col s12 m6 l3"><a class="active" href="#set1">{{ 'howto.tab_menu.add_link'|trans }}</a></li>
|
||||
<li class="tab col s12 m6 l3"><a href="#set2">{{ 'howto.tab_menu.shortcuts'|trans }}</a></li>
|
||||
</ul>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<span class="black-text">{{ form_errors(form.label) }}</span>
|
||||
{% endif %}
|
||||
|
||||
{{ form_widget(form.label, {'attr': {'autocomplete': 'off'}}) }}
|
||||
{{ form_widget(form.label, {'attr': {'autocomplete': 'off', 'data-add-tag-target': 'input'}}) }}
|
||||
|
||||
{{ form_widget(form.add, {'attr': {'class': 'btn waves-effect waves-light tags-add-form-submit'}}) }}
|
||||
{{ form_widget(form._token) }}
|
||||
|
||||
@ -19,18 +19,19 @@
|
||||
{% for tagWithNbEntries in allTagsWithNbEntries %}
|
||||
{% set tag = tagWithNbEntries.tag %}
|
||||
{% set nbEntries = tagWithNbEntries.nbEntries %}
|
||||
<li title="{{ tag.label }} ({{ nbEntries }})" id="tag-{{ tag.id }}" class="chip">
|
||||
<a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link" data-handle="tag-link">
|
||||
<li title="{{ tag.label }} ({{ nbEntries }})" id="tag-{{ tag.id }}" class="chip" data-controller="tag">
|
||||
<a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link" data-tag-target="link">
|
||||
{{ tag.label }} ({{ nbEntries }})
|
||||
</a>
|
||||
{% if renameForms is defined and renameForms[tag.id] is defined and is_granted('EDIT', tag) %}
|
||||
<form class="card-tag-form hidden" data-handle="tag-rename-form" action="{{ path('tag_rename', {'slug': tag.slug, redirect: current_path}) }}" method="POST">
|
||||
{{ form_widget(renameForms[tag.id].label, {'attr': {'value': tag.label}}) }}
|
||||
<form class="card-tag-form hidden" data-tag-target="form" action="{{ path('tag_rename', {'slug': tag.slug, redirect: current_path}) }}" method="POST">
|
||||
{{ form_widget(renameForms[tag.id].label, {'attr': {'value': tag.label, 'data-tag-target': 'input'}}) }}
|
||||
{{ form_rest(renameForms[tag.id]) }}
|
||||
<button type="submit"><i class="material-icons">done</i></button>
|
||||
</form>
|
||||
<a class="card-tag-icon card-tag-rename" data-handler="tag-rename" href="javascript:void(0);">
|
||||
<button type="button" data-tag-target="edit" data-action="tag#showForm">
|
||||
<i class="material-icons">mode_edit</i>
|
||||
</a>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if is_granted('DELETE', tag) %}
|
||||
<a id="delete-{{ tag.slug }}" href="{{ path('tag_delete', {'slug': tag.slug, redirect: current_path}) }}" class="card-tag-icon card-tag-delete" onclick="return confirm('{{ 'tag.confirm.delete'|trans({'%name%': tag.label})|escape('js') }}')">
|
||||
|
||||
@ -48,16 +48,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" data-controller="fake-radio">
|
||||
<div class="input-field col s12">
|
||||
<label for="{{ edit_form.emailTwoFactor.vars.id }}">
|
||||
{{ form_widget(edit_form.emailTwoFactor) }}
|
||||
{{ form_widget(edit_form.emailTwoFactor, {attr: {'data-fake-radio-target': 'emailTwoFactor', 'data-action': 'fake-radio#uncheckGoogle'}}) }}
|
||||
<span>{{ edit_form.emailTwoFactor.vars.label|trans }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-field col s12">
|
||||
<label for="{{ edit_form.googleTwoFactor.vars.id }}">
|
||||
{{ form_widget(edit_form.googleTwoFactor) }}
|
||||
{{ form_widget(edit_form.googleTwoFactor, {attr: {'data-fake-radio-target': 'googleTwoFactor', 'data-action': 'fake-radio#uncheckEmail'}}) }}
|
||||
<span>{{ edit_form.googleTwoFactor.vars.label|trans }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<!--[if lte IE 7]><html class="no-js ie7 ie67 ie678"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
|
||||
<!--[if IE 8]><html class="no-js ie8 ie678"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
|
||||
<!--[if gt IE 8]><html class="no-js"{% if lang is not empty %} lang="{{ lang }}"{% endif %}><![endif]-->
|
||||
<html{% if lang is not empty %} class="{{ theme_class() }}" lang="{{ lang }}"{% endif %}>
|
||||
<html{% if lang is not empty %} lang="{{ lang }}"{% endif %} class="{{ theme_class() }}" data-controller="dark-theme shortcuts" data-shortcuts-entries-navigation-outlet="[data-controller~='entries-navigation']">
|
||||
<head>
|
||||
{% block head %}
|
||||
<meta name="viewport" content="initial-scale=1.0">
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="div_tabs col s12">
|
||||
<ul class="tabs">
|
||||
<ul class="tabs" data-controller="materialize--tabs">
|
||||
{% for section in sections|craue_sortSections %}
|
||||
<li class="tab col s12 m6 l3"><a href="#set-{{ section }}">{{ section|trans({}, 'CraueConfigBundle') }}</a></li>
|
||||
{% endfor %}
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<div class="card-content">
|
||||
|
||||
{% if error %}
|
||||
<p class="material-toast hidden">{{ error.messageKey|trans(error.messageData, 'security') }}</p>
|
||||
<p class="hidden" data-controller="materialize--toast">{{ error.messageKey|trans(error.messageData, 'security') }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% for flash_message in app.session.flashbag.get('notice') %}
|
||||
<p class="material-toast hidden">{{ flash_message }}</p>
|
||||
<p class="hidden" data-controller="materialize--toast">{{ flash_message }}</p>
|
||||
{% endfor %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
{% block messages %}
|
||||
{% for flash_message in app.session.flashbag.get('notice') %}
|
||||
<p class="material-toast hidden">{{ flash_message|trans }}</p>
|
||||
<p class="hidden" data-controller="materialize--toast">{{ flash_message|trans }}</p>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<nav class="cyan darken-1">
|
||||
<ul id="slide-out" class="left-bar sidenav sidenav-fixed">
|
||||
<ul id="slide-out" class="left-bar sidenav sidenav-fixed" data-controller="materialize--sidenav">
|
||||
{% block logo %}
|
||||
<li class="logo border-bottom">
|
||||
{% if is_granted('LIST_ENTRIES') %}
|
||||
@ -79,8 +79,8 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="nav-panels">
|
||||
<div class="nav-panel-actions nav-panel-item">
|
||||
<div class="nav-panels" data-controller="topbar">
|
||||
<div class="nav-panel-actions nav-panel-item" data-topbar-target="actions">
|
||||
<div class="nav-panel-top">
|
||||
<a href="#" data-target="slide-out" class="nav-panel-menu sidenav-trigger"><i class="material-icons">menu</i></a>
|
||||
<h1 class="left action">
|
||||
@ -90,18 +90,18 @@
|
||||
</div>
|
||||
<ul class="input-field nav-panel-buttom">
|
||||
<li class="bold toggle-add-url-container">
|
||||
<a class="waves-effect tooltipped toggle-add-url" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.add_new_entry'|trans }}" href="{{ path('new') }}" id="nav-btn-add">
|
||||
<a class="waves-effect toggle-add-url" data-controller="materialize--tooltip" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.add_new_entry'|trans }}" href="{{ path('new') }}" data-action="topbar#showAddUrl:prevent:stop" data-shortcuts-target="showAddUrl">
|
||||
<i class="material-icons">add</i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="waves-effect tooltipped" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.search'|trans }}" href="javascript: void(null);" id="nav-btn-search">
|
||||
<a class="waves-effect" data-controller="materialize--tooltip" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.search'|trans }}" href="javascript: void(null);" data-action="topbar#showSearch:prevent:stop" data-shortcuts-target="showSearch">
|
||||
<i class="material-icons">search</i>
|
||||
</a>
|
||||
</li>
|
||||
{% block nav_panel_extra_actions '' %}
|
||||
<li class="bold">
|
||||
<a class="wave-effect tooltipped dropdown-trigger" data-covertrigger="false" data-constrainwidth="false" data-target="dropdown-account" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.account'|trans }}" href="#" id="news_menu">
|
||||
<a class="wave-effect dropdown-trigger" data-controller="materialize--dropdown materialize--tooltip" data-target="dropdown-account" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.account'|trans }}" href="#" id="news_menu">
|
||||
<i class="material-icons" id="news_link">account_circle</i>
|
||||
</a>
|
||||
</li>
|
||||
@ -134,19 +134,19 @@
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="#" class="js-theme-toggle" data-theme="light">
|
||||
<a href="#" data-action="click->dark-theme#useLight:prevent">
|
||||
<i class="theme-toggle-icon material-icons tiny">brightness_high</i>
|
||||
<span>{{ 'menu.left.theme_toggle_light'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="js-theme-toggle" data-theme="dark">
|
||||
<a href="#" data-action="click->dark-theme#useDark:prevent">
|
||||
<i class="theme-toggle-icon material-icons tiny">brightness_low</i>
|
||||
<span>{{ 'menu.left.theme_toggle_dark'|trans }}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="js-theme-toggle" data-theme="auto">
|
||||
<a href="#" data-action="click->dark-theme#useAuto:prevent">
|
||||
<i class="theme-toggle-icon material-icons tiny">brightness_auto</i>
|
||||
<span>{{ 'menu.left.theme_toggle_auto'|trans }}</span>
|
||||
</a>
|
||||
|
||||
@ -43,8 +43,6 @@ class WallabagRestControllerTest extends WallabagApiTestCase
|
||||
|
||||
if (!$client->getContainer()->getParameter('fosuser_registration')) {
|
||||
$this->markTestSkipped('fosuser_registration is not enabled.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$client->getContainer()->get(Config::class)->set('api_user_registration', 1);
|
||||
|
||||
@ -11,8 +11,6 @@ class ExportControllerTest extends WallabagTestCase
|
||||
{
|
||||
private $adminEntry;
|
||||
private $bobEntry;
|
||||
private $sameDomainEntry;
|
||||
private $sameDomainEntry2;
|
||||
|
||||
public function testLogin()
|
||||
{
|
||||
|
||||
@ -88,8 +88,6 @@ class SecurityControllerTest extends WallabagTestCase
|
||||
|
||||
if (!$client->getContainer()->getParameter('fosuser_registration')) {
|
||||
$this->markTestSkipped('fosuser_registration is not enabled.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$client->followRedirects();
|
||||
|
||||
@ -55,7 +55,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertEmpty($entry->getPreviewPicture());
|
||||
$this->assertEmpty($entry->getMimetype());
|
||||
$this->assertEmpty($entry->getLanguage());
|
||||
$this->assertSame(0.0, $entry->getReadingTime());
|
||||
$this->assertSame(0, $entry->getReadingTime());
|
||||
$this->assertNull($entry->getDomainName());
|
||||
$this->assertTrue($entry->isNotParsed());
|
||||
}
|
||||
@ -95,7 +95,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertEmpty($entry->getPreviewPicture());
|
||||
$this->assertEmpty($entry->getMimetype());
|
||||
$this->assertEmpty($entry->getLanguage());
|
||||
$this->assertSame(0.0, $entry->getReadingTime());
|
||||
$this->assertSame(0, $entry->getReadingTime());
|
||||
$this->assertSame('0.0.0.0', $entry->getDomainName());
|
||||
$this->assertTrue($entry->isNotParsed());
|
||||
}
|
||||
@ -138,7 +138,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertEmpty($entry->getLanguage());
|
||||
$this->assertEmpty($entry->getHttpStatus());
|
||||
$this->assertEmpty($entry->getMimetype());
|
||||
$this->assertSame(0.0, $entry->getReadingTime());
|
||||
$this->assertSame(0, $entry->getReadingTime());
|
||||
$this->assertSame('domain.io', $entry->getDomainName());
|
||||
$this->assertTrue($entry->isNotParsed());
|
||||
}
|
||||
@ -184,7 +184,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame('200', $entry->getHttpStatus());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
}
|
||||
@ -230,7 +230,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame('200', $entry->getHttpStatus());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
}
|
||||
@ -275,7 +275,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame('200', $entry->getHttpStatus());
|
||||
$this->assertSame(0.0, $entry->getReadingTime());
|
||||
$this->assertSame(0, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
}
|
||||
@ -320,7 +320,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame('200', $entry->getHttpStatus());
|
||||
$this->assertSame(0.0, $entry->getReadingTime());
|
||||
$this->assertSame(0, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
}
|
||||
@ -368,7 +368,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertNull($entry->getLanguage());
|
||||
$this->assertSame('200', $entry->getHttpStatus());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
}
|
||||
@ -422,7 +422,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame('200', $entry->getHttpStatus());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
}
|
||||
@ -461,7 +461,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertStringContainsString('content', $entry->getContent());
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertSame('24/03/2014', $entry->getPublishedAt()->format('d/m/Y'));
|
||||
$this->assertContains('Jeremy', $entry->getPublishedBy());
|
||||
@ -505,7 +505,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertStringContainsString('content', $entry->getContent());
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertSame('08/09/2016', $entry->getPublishedAt()->format('d/m/Y'));
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
@ -545,7 +545,7 @@ class ContentProxyTest extends TestCase
|
||||
$this->assertStringContainsString('content', $entry->getContent());
|
||||
$this->assertSame('text/html', $entry->getMimetype());
|
||||
$this->assertSame('fr', $entry->getLanguage());
|
||||
$this->assertSame(4.0, $entry->getReadingTime());
|
||||
$this->assertSame(4, $entry->getReadingTime());
|
||||
$this->assertSame('1.1.1.1', $entry->getDomainName());
|
||||
$this->assertNull($entry->getPublishedAt());
|
||||
$this->assertFalse($entry->isNotParsed());
|
||||
@ -883,42 +883,42 @@ class ContentProxyTest extends TestCase
|
||||
*/
|
||||
$this->markTestSkipped('Encoding issue in PHP >= 8.1');
|
||||
|
||||
// See http://graphemica.com for more info about the characters
|
||||
// '😻ℤ<F09F98BB>z' (U+1F63B or F09F98BB; U+2124 or E284A4; invalid character 81; U+007A or 7A) in hexadecimal and UTF-8
|
||||
// 0x81 is not a valid character for UTF16, UTF8 and WINDOWS-1252
|
||||
$actualTitle = $this->hexToStr('F09F98BB' . 'E284A4' . '81' . '7A');
|
||||
|
||||
$tagger = $this->getTaggerMock();
|
||||
$tagger->expects($this->once())
|
||||
->method('tag');
|
||||
|
||||
$ruleBasedIgnoreOriginProcessor = $this->getRuleBasedIgnoreOriginProcessorMock();
|
||||
|
||||
$graby = $this->getMockBuilder(Graby::class)
|
||||
->onlyMethods(['fetchContent'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$graby->expects($this->any())
|
||||
->method('fetchContent')
|
||||
->willReturn([
|
||||
'html' => false,
|
||||
'title' => $actualTitle,
|
||||
'url' => '',
|
||||
'headers' => [
|
||||
'content-type' => 'application/pdf',
|
||||
],
|
||||
'language' => '',
|
||||
]);
|
||||
|
||||
$proxy = new ContentProxy($graby, $tagger, $ruleBasedIgnoreOriginProcessor, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
$entry = new Entry(new User());
|
||||
$proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
|
||||
// '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
|
||||
// the 0x81 (represented by <20>) is invalid for UTF16, UTF8 and WINDOWS-1252 and is removed
|
||||
$expectedTitle = 'F09F98BB' . 'E284A4' . '7A';
|
||||
$this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
// // See http://graphemica.com for more info about the characters
|
||||
// // '😻ℤ<F09F98BB>z' (U+1F63B or F09F98BB; U+2124 or E284A4; invalid character 81; U+007A or 7A) in hexadecimal and UTF-8
|
||||
// // 0x81 is not a valid character for UTF16, UTF8 and WINDOWS-1252
|
||||
// $actualTitle = $this->hexToStr('F09F98BB' . 'E284A4' . '81' . '7A');
|
||||
//
|
||||
// $tagger = $this->getTaggerMock();
|
||||
// $tagger->expects($this->once())
|
||||
// ->method('tag');
|
||||
//
|
||||
// $ruleBasedIgnoreOriginProcessor = $this->getRuleBasedIgnoreOriginProcessorMock();
|
||||
//
|
||||
// $graby = $this->getMockBuilder(Graby::class)
|
||||
// ->onlyMethods(['fetchContent'])
|
||||
// ->disableOriginalConstructor()
|
||||
// ->getMock();
|
||||
//
|
||||
// $graby->expects($this->any())
|
||||
// ->method('fetchContent')
|
||||
// ->willReturn([
|
||||
// 'html' => false,
|
||||
// 'title' => $actualTitle,
|
||||
// 'url' => '',
|
||||
// 'headers' => [
|
||||
// 'content-type' => 'application/pdf',
|
||||
// ],
|
||||
// 'language' => '',
|
||||
// ]);
|
||||
//
|
||||
// $proxy = new ContentProxy($graby, $tagger, $ruleBasedIgnoreOriginProcessor, $this->getValidator(), $this->getLogger(), $this->fetchingErrorMessage);
|
||||
// $entry = new Entry(new User());
|
||||
// $proxy->updateEntry($entry, 'http://0.0.0.0');
|
||||
//
|
||||
// // '😻ℤz' (U+1F63B or F09F98BB; U+2124 or E284A4; U+007A or 7A) in hexadecimal and UTF-8
|
||||
// // the 0x81 (represented by <20>) is invalid for UTF16, UTF8 and WINDOWS-1252 and is removed
|
||||
// $expectedTitle = 'F09F98BB' . 'E284A4' . '7A';
|
||||
// $this->assertSame($expectedTitle, $this->strToHex($entry->getTitle()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -13,7 +13,7 @@ class UtilsTest extends TestCase
|
||||
*/
|
||||
public function testCorrectWordsCountForDifferentLanguages($filename, $text, $expectedCount)
|
||||
{
|
||||
static::assertSame((float) $expectedCount, Utils::getReadingTime($text), 'Reading time for: ' . $filename);
|
||||
static::assertSame($expectedCount, Utils::getReadingTime($text), 'Reading time for: ' . $filename);
|
||||
}
|
||||
|
||||
public function examples()
|
||||
@ -30,7 +30,7 @@ class UtilsTest extends TestCase
|
||||
$examples[] = [
|
||||
$file->getRelativePathname(),
|
||||
$match[1], // content
|
||||
$match[2], // reading time
|
||||
(int) $match[2], // reading time
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ Encore
|
||||
.addEntry('main', './assets/index.js')
|
||||
.addEntry('public', './assets/share.js')
|
||||
.splitEntryChunks()
|
||||
.enableStimulusBridge('./assets/controllers.json')
|
||||
.enableSingleRuntimeChunk()
|
||||
.cleanupOutputBeforeBuild()
|
||||
.enableBuildNotifications()
|
||||
@ -21,7 +22,6 @@ Encore
|
||||
config.corejs = '3.23';
|
||||
})
|
||||
.enableSassLoader()
|
||||
.enablePostCssLoader()
|
||||
.autoProvidejQuery();
|
||||
.enablePostCssLoader();
|
||||
|
||||
module.exports = Encore.getWebpackConfig();
|
||||
|
||||
38
yarn.lock
38
yarn.lock
@ -911,6 +911,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/oswald/-/oswald-5.2.5.tgz#01ef5b61fae44542eb22330255d32b728ee45b0c"
|
||||
integrity sha512-Sw8ayEYCoOzG2ISw5HaX3d5ILt3OEG2VFX2nzHaGywYD9p0WvVfO4SBK5/y9JGuOty3jA6OhptxOYZgwjTTPLQ==
|
||||
|
||||
"@hotwired/stimulus-webpack-helpers@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz#4cd74487adeca576c9865ac2b9fe5cb20cef16dd"
|
||||
integrity sha512-wa/zupVG0eWxRYJjC1IiPBdt3Lruv0RqGN+/DTMmUWUyMAEB27KXmVY6a8YpUVTM7QwVuaLNGW4EqDgrS2upXQ==
|
||||
|
||||
"@hotwired/stimulus@^3.2.2":
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608"
|
||||
integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==
|
||||
|
||||
"@humanwhocodes/config-array@^0.13.0":
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
|
||||
@ -1056,6 +1066,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
|
||||
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
|
||||
|
||||
"@symfony/stimulus-bridge@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@symfony/stimulus-bridge/-/stimulus-bridge-4.0.0.tgz#dbd352a373f8ff323f2895e0bb29f22b9abcc122"
|
||||
integrity sha512-BfeXHAyNtE2hFJtTKFS8vLW2ElqNyzUZJJ4TMeUhrjBjYVt4/myeQxpLidy995l+TEzryE7YrnShqLviBr3pAg==
|
||||
dependencies:
|
||||
"@hotwired/stimulus-webpack-helpers" "^1.0.1"
|
||||
"@types/webpack-env" "^1.16.4"
|
||||
loader-utils "^2.0.0 || ^3.0.0"
|
||||
schema-utils "^3.0.0 || ^4.0.0"
|
||||
|
||||
"@symfony/webpack-encore@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@symfony/webpack-encore/-/webpack-encore-5.1.0.tgz#d5664153136959e3baf3e248c9b55350c2c81a6d"
|
||||
@ -1149,6 +1169,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
|
||||
integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==
|
||||
|
||||
"@types/webpack-env@^1.16.4":
|
||||
version "1.18.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.8.tgz#71f083718c094204d7b64443701d32f1db3989e3"
|
||||
integrity sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==
|
||||
|
||||
"@types/yargs-parser@*":
|
||||
version "21.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
|
||||
@ -1371,7 +1396,6 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.9.0:
|
||||
|
||||
annotator@wallabag/annotator#master:
|
||||
version "2.0.0-alpha.4"
|
||||
uid "082069d777ed0fe5080392e85675ef3a519c7886"
|
||||
resolved "https://codeload.github.com/wallabag/annotator/tar.gz/082069d777ed0fe5080392e85675ef3a519c7886"
|
||||
dependencies:
|
||||
backbone-extend-standalone "^0.1.2"
|
||||
@ -3353,11 +3377,6 @@ jiti@^1.20.0:
|
||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
|
||||
integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
|
||||
|
||||
jquery.cookie@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery.cookie/-/jquery.cookie-1.4.1.tgz#d63dce209eab691fe63316db08ca9e47e0f9385b"
|
||||
integrity sha512-c/hZOOL+8VSw/FkTVH637gS1/6YzMSCROpTZ2qBYwJ7s7sHajU7uBkSSiE5+GXWwrfCCyO+jsYjUQ7Hs2rIxAA==
|
||||
|
||||
jquery@^3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de"
|
||||
@ -3481,6 +3500,11 @@ loader-utils@^2.0.0:
|
||||
emojis-list "^3.0.0"
|
||||
json5 "^2.1.2"
|
||||
|
||||
"loader-utils@^2.0.0 || ^3.0.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.3.1.tgz#735b9a19fd63648ca7adbd31c2327dfe281304e5"
|
||||
integrity sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==
|
||||
|
||||
locate-path@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
|
||||
@ -4680,7 +4704,7 @@ schema-utils@^3.0.0:
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0:
|
||||
"schema-utils@^3.0.0 || ^4.0.0", schema-utils@^4.0.0, schema-utils@^4.2.0, schema-utils@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.0.tgz#3b669f04f71ff2dfb5aba7ce2d5a9d79b35622c0"
|
||||
integrity sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==
|
||||
|
||||
Reference in New Issue
Block a user