mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Addressed existing cm6 todos
- Updated clipboard handling - Removed old clipboard package for browser-native API. - Updated codemirror editor events to use new props for new data types.
This commit is contained in:
parent
fdda813d5f
commit
257a703878
34
package-lock.json
generated
34
package-lock.json
generated
@ -15,7 +15,6 @@
|
|||||||
"@codemirror/view": "^6.1.2",
|
"@codemirror/view": "^6.1.2",
|
||||||
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
||||||
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
||||||
"clipboard": "^2.0.11",
|
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"dropzone": "^5.9.3",
|
"dropzone": "^5.9.3",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
@ -810,16 +809,6 @@
|
|||||||
"node": ">= 8.10.0"
|
"node": ">= 8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/clipboard": {
|
|
||||||
"version": "2.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
|
|
||||||
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
|
|
||||||
"dependencies": {
|
|
||||||
"good-listener": "^1.2.2",
|
|
||||||
"select": "^1.1.2",
|
|
||||||
"tiny-emitter": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cliui": {
|
"node_modules/cliui": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||||
@ -912,11 +901,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/delegate": {
|
|
||||||
"version": "3.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
|
||||||
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
|
|
||||||
},
|
|
||||||
"node_modules/dropzone": {
|
"node_modules/dropzone": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz",
|
||||||
@ -1218,14 +1202,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/good-listener": {
|
|
||||||
"version": "1.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
|
||||||
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
|
|
||||||
"dependencies": {
|
|
||||||
"delegate": "^3.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
@ -2062,11 +2038,6 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/select": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||||
@ -2289,11 +2260,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tiny-emitter": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
"@codemirror/view": "^6.1.2",
|
"@codemirror/view": "^6.1.2",
|
||||||
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
||||||
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
||||||
"clipboard": "^2.0.11",
|
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"dropzone": "^5.9.3",
|
"dropzone": "^5.9.3",
|
||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
|
@ -134,7 +134,6 @@ Note: This is not an exhaustive list of all libraries and projects that would be
|
|||||||
* [Sortable](https://github.com/SortableJS/Sortable) - _[MIT](https://github.com/SortableJS/Sortable/blob/master/LICENSE)_
|
* [Sortable](https://github.com/SortableJS/Sortable) - _[MIT](https://github.com/SortableJS/Sortable/blob/master/LICENSE)_
|
||||||
* [Google Material Icons](https://github.com/google/material-design-icons) - _[Apache-2.0](https://github.com/google/material-design-icons/blob/master/LICENSE)_
|
* [Google Material Icons](https://github.com/google/material-design-icons) - _[Apache-2.0](https://github.com/google/material-design-icons/blob/master/LICENSE)_
|
||||||
* [Dropzone.js](http://www.dropzonejs.com/) - _[MIT](https://github.com/dropzone/dropzone/blob/main/LICENSE)_
|
* [Dropzone.js](http://www.dropzonejs.com/) - _[MIT](https://github.com/dropzone/dropzone/blob/main/LICENSE)_
|
||||||
* [clipboard.js](https://clipboardjs.com/) - _[MIT](https://github.com/zenorocha/clipboard.js/blob/master/LICENSE)_
|
|
||||||
* [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists) - _[MIT](https://github.com/markdown-it/markdown-it/blob/master/LICENSE) and [ISC](https://github.com/revin/markdown-it-task-lists/blob/master/LICENSE)_
|
* [markdown-it](https://github.com/markdown-it/markdown-it) and [markdown-it-task-lists](https://github.com/revin/markdown-it-task-lists) - _[MIT](https://github.com/markdown-it/markdown-it/blob/master/LICENSE) and [ISC](https://github.com/revin/markdown-it-task-lists/blob/master/LICENSE)_
|
||||||
* [Dompdf](https://github.com/dompdf/dompdf) - _[LGPL v2.1](https://github.com/dompdf/dompdf/blob/master/LICENSE.LGPL)_
|
* [Dompdf](https://github.com/dompdf/dompdf) - _[LGPL v2.1](https://github.com/dompdf/dompdf/blob/master/LICENSE.LGPL)_
|
||||||
* [BarryVD/Dompdf](https://github.com/barryvdh/laravel-dompdf) - _[MIT](https://github.com/barryvdh/laravel-dompdf/blob/master/LICENSE)_
|
* [BarryVD/Dompdf](https://github.com/barryvdh/laravel-dompdf) - _[MIT](https://github.com/barryvdh/laravel-dompdf/blob/master/LICENSE)_
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {EditorView, keymap} from "@codemirror/view"
|
import {EditorView, keymap} from "@codemirror/view";
|
||||||
import Clipboard from "clipboard/dist/clipboard.min";
|
import {copyTextToClipboard} from "../services/clipboard.js"
|
||||||
|
|
||||||
// Modes
|
// Modes
|
||||||
import {viewer, editor} from "./setups.js";
|
import {viewer, editor} from "./setups.js";
|
||||||
@ -57,28 +57,23 @@ function highlightElem(elem) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a button to a CodeMirror instance which copies the contents to the clipboard upon click.
|
* Add a button to a CodeMirror instance which copies the contents to the clipboard upon click.
|
||||||
* @param cmInstance
|
* @param {EditorView} editorView
|
||||||
*/
|
*/
|
||||||
function addCopyIcon(cmInstance) {
|
function addCopyIcon(editorView) {
|
||||||
// TODO
|
const copyIcon = `<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
|
||||||
// const copyIcon = `<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>`;
|
const copyButton = document.createElement('button');
|
||||||
// const copyButton = document.createElement('div');
|
copyButton.setAttribute('type', 'button')
|
||||||
// copyButton.classList.add('CodeMirror-copy');
|
copyButton.classList.add('cm-copy-button');
|
||||||
// copyButton.innerHTML = copyIcon;
|
copyButton.innerHTML = copyIcon;
|
||||||
// cmInstance.display.wrapper.appendChild(copyButton);
|
editorView.dom.appendChild(copyButton);
|
||||||
//
|
|
||||||
// const clipboard = new Clipboard(copyButton, {
|
copyButton.addEventListener('click', event => {
|
||||||
// text: function(trigger) {
|
copyTextToClipboard(editorView.state.doc.toString());
|
||||||
// return cmInstance.getValue()
|
copyButton.classList.add('success');
|
||||||
// }
|
setTimeout(() => {
|
||||||
// });
|
copyButton.classList.remove('success');
|
||||||
//
|
}, 240);
|
||||||
// clipboard.on('success', event => {
|
});
|
||||||
// copyButton.classList.add('success');
|
|
||||||
// setTimeout(() => {
|
|
||||||
// copyButton.classList.remove('success');
|
|
||||||
// }, 240);
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,11 +182,7 @@ export function updateLayout(cmInstance) {
|
|||||||
*/
|
*/
|
||||||
export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) {
|
export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) {
|
||||||
const content = elem.textContent;
|
const content = elem.textContent;
|
||||||
|
const config = {
|
||||||
// TODO - Change to pass something else that's useful, probably extension array?
|
|
||||||
// window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {config});
|
|
||||||
|
|
||||||
const ev = createView({
|
|
||||||
parent: elem.parentNode,
|
parent: elem.parentNode,
|
||||||
doc: content,
|
doc: content,
|
||||||
extensions: [
|
extensions: [
|
||||||
@ -202,8 +193,13 @@ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) {
|
|||||||
EditorView.domEventHandlers(domEventHandlers),
|
EditorView.domEventHandlers(domEventHandlers),
|
||||||
keymap.of(keyBindings),
|
keymap.of(keyBindings),
|
||||||
],
|
],
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// Emit a pre-event public event to allow tweaking of the configure before view creation.
|
||||||
|
window.$events.emitPublic(elem, 'editor-markdown-cm::pre-init', {cmEditorViewConfig: config});
|
||||||
|
|
||||||
|
// Create editor view, hide original input
|
||||||
|
const ev = createView(config);
|
||||||
elem.style.display = 'none';
|
elem.style.display = 'none';
|
||||||
|
|
||||||
return ev;
|
return ev;
|
||||||
|
@ -45,5 +45,6 @@ export function editor(language) {
|
|||||||
...historyKeymap,
|
...historyKeymap,
|
||||||
]),
|
]),
|
||||||
getLanguageExtension(language, ''),
|
getLanguageExtension(language, ''),
|
||||||
|
EditorView.lineWrapping,
|
||||||
];
|
];
|
||||||
}
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import {debounce} from "../services/util";
|
|
||||||
import {Component} from "./component";
|
import {Component} from "./component";
|
||||||
import {init as initEditor} from "../markdown/editor";
|
import {init as initEditor} from "../markdown/editor";
|
||||||
|
|
||||||
@ -45,8 +44,7 @@ export class MarkdownEditor extends Component {
|
|||||||
window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
|
window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
|
||||||
markdownIt: this.editor.markdown.getRenderer(),
|
markdownIt: this.editor.markdown.getRenderer(),
|
||||||
displayEl: this.display,
|
displayEl: this.display,
|
||||||
// TODO - change to codeMirrorView?
|
cmEditorView: this.editor.cm,
|
||||||
// codeMirrorInstance: this.editor.cm,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,12 +79,6 @@ export class MarkdownEditor extends Component {
|
|||||||
toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
|
toolbarLabel.closest('.markdown-editor-wrap').classList.add('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Refresh CodeMirror on container resize
|
|
||||||
// TODO
|
|
||||||
// const resizeDebounced = debounce(() => this.editor.cm.refresh(), 100, false);
|
|
||||||
// const observer = new ResizeObserver(resizeDebounced);
|
|
||||||
// observer.observe(this.elem);
|
|
||||||
|
|
||||||
this.handleDividerDrag();
|
this.handleDividerDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +96,6 @@ export class MarkdownEditor extends Component {
|
|||||||
window.removeEventListener('pointerup', upListener);
|
window.removeEventListener('pointerup', upListener);
|
||||||
this.display.style.pointerEvents = null;
|
this.display.style.pointerEvents = null;
|
||||||
document.body.style.userSelect = null;
|
document.body.style.userSelect = null;
|
||||||
// TODO
|
|
||||||
// this.editor.cm.refresh();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.display.style.pointerEvents = 'none';
|
this.display.style.pointerEvents = 'none';
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import * as DOM from "../services/dom";
|
import * as DOM from "../services/dom";
|
||||||
import Clipboard from "clipboard/dist/clipboard.min";
|
|
||||||
import {Component} from "./component";
|
import {Component} from "./component";
|
||||||
|
import {copyTextToClipboard} from "../services/clipboard";
|
||||||
|
|
||||||
|
|
||||||
export class Pointer extends Component {
|
export class Pointer extends Component {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.container = this.$el;
|
this.container = this.$el;
|
||||||
|
this.input = this.$refs.input;
|
||||||
|
this.button = this.$refs.button;
|
||||||
this.pageId = this.$opts.pageId;
|
this.pageId = this.$opts.pageId;
|
||||||
|
|
||||||
// Instance variables
|
// Instance variables
|
||||||
@ -16,15 +18,17 @@ export class Pointer extends Component {
|
|||||||
this.pointerSectionId = '';
|
this.pointerSectionId = '';
|
||||||
|
|
||||||
this.setupListeners();
|
this.setupListeners();
|
||||||
|
|
||||||
// Set up clipboard
|
|
||||||
new Clipboard(this.container.querySelector('button'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupListeners() {
|
setupListeners() {
|
||||||
|
// Copy on copy button click
|
||||||
|
this.button.addEventListener('click', event => {
|
||||||
|
copyTextToClipboard(this.input.value);
|
||||||
|
});
|
||||||
|
|
||||||
// Select all contents on input click
|
// Select all contents on input click
|
||||||
DOM.onChildEvent(this.container, 'input', 'click', (event, input) => {
|
this.input.addEventListener('click', event => {
|
||||||
input.select();
|
this.input.select();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -112,7 +116,7 @@ export class Pointer extends Component {
|
|||||||
inputText = window.location.protocol + "//" + window.location.host + inputText;
|
inputText = window.location.protocol + "//" + window.location.host + inputText;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container.querySelector('input').value = inputText;
|
this.input.value = inputText;
|
||||||
|
|
||||||
// Update anchor if present
|
// Update anchor if present
|
||||||
const editAnchor = this.container.querySelector('#pointer-edit');
|
const editAnchor = this.container.querySelector('#pointer-edit');
|
||||||
|
@ -64,11 +64,10 @@ export async function init(editor) {
|
|||||||
domEventHandlers,
|
domEventHandlers,
|
||||||
provideKeyBindings(editor),
|
provideKeyBindings(editor),
|
||||||
);
|
);
|
||||||
window.cm = cm;
|
|
||||||
|
|
||||||
// Will force to remain as ltr for now due to issues when HTML is in editor.
|
// Add editor view to window for easy access/debugging.
|
||||||
// TODO
|
// Not part of official API/Docs
|
||||||
// cm.setOption('direction', 'ltr');
|
window.mdEditorView = cm;
|
||||||
|
|
||||||
return cm;
|
return cm;
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
class Clipboard {
|
export class Clipboard {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -51,4 +51,8 @@ class Clipboard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function copyTextToClipboard(text) {
|
||||||
|
return navigator.clipboard.writeText(text);
|
||||||
|
}
|
||||||
|
|
||||||
export default Clipboard;
|
export default Clipboard;
|
@ -450,7 +450,7 @@ html.dark-mode .CodeMirror pre {
|
|||||||
/**
|
/**
|
||||||
* Custom Copy Button
|
* Custom Copy Button
|
||||||
*/
|
*/
|
||||||
.CodeMirror-copy {
|
.cm-copy-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
right: -1px;
|
right: -1px;
|
||||||
@ -478,7 +478,7 @@ html.dark-mode .CodeMirror pre {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.CodeMirror:hover .CodeMirror-copy {
|
.cm-editor:hover .cm-copy-button {
|
||||||
user-select: all;
|
user-select: all;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div markdown-input class="flex flex-fill">
|
<div class="flex flex-fill" dir="ltr">
|
||||||
<textarea id="markdown-editor-input"
|
<textarea id="markdown-editor-input"
|
||||||
refs="markdown-editor@input"
|
refs="markdown-editor@input"
|
||||||
@if($errors->has('markdown')) class="text-neg" @endif
|
@if($errors->has('markdown')) class="text-neg" @endif
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
<div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
|
<div class="pointer anim {{ userCan('page-update', $page) ? 'is-page-editable' : ''}}" >
|
||||||
<span class="icon mr-xxs">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
|
<span class="icon mr-xxs">@icon('link') @icon('include', ['style' => 'display:none;'])</span>
|
||||||
<div class="input-group inline block">
|
<div class="input-group inline block">
|
||||||
<input readonly="readonly" type="text" id="pointer-url" placeholder="url">
|
<input refs="pointer@input" readonly="readonly" type="text" id="pointer-url" placeholder="url">
|
||||||
<button class="button outline icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
|
<button refs="pointer@button" class="button outline icon" data-clipboard-target="#pointer-url" type="button" title="{{ trans('entities.pages_copy_link') }}">@icon('copy')</button>
|
||||||
</div>
|
</div>
|
||||||
@if(userCan('page-update', $page))
|
@if(userCan('page-update', $page))
|
||||||
<a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
|
<a href="{{ $page->getUrl('/edit') }}" id="pointer-edit" data-edit-href="{{ $page->getUrl('/edit') }}"
|
||||||
|
Loading…
Reference in New Issue
Block a user