mirror of
https://github.com/discourse/discourse.git
synced 2025-02-20 13:24:58 +08:00
FEATURE: experimental fast edit (#14340)
Fast edit allows you to quickly edit a typo in a post, this is experimental ATM and behind a site setting: `enable_fast_edit` (default false)
This commit is contained in:
parent
27bad28c53
commit
b83868bfb0
|
@ -1,3 +1,6 @@
|
|||
import { propertyEqual } from "discourse/lib/computed";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import {
|
||||
postUrl,
|
||||
selectedElement,
|
||||
|
@ -28,11 +31,27 @@ function getQuoteTitle(element) {
|
|||
return titleEl.textContent.trim().replace(/:$/, "");
|
||||
}
|
||||
|
||||
function fixQuotes(str) {
|
||||
return str.replace(/‘|’|„|“|«|»|”/g, '"');
|
||||
}
|
||||
|
||||
function regexSafeStr(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ["quote-button"],
|
||||
classNameBindings: ["visible"],
|
||||
visible: false,
|
||||
privateCategory: alias("topic.category.read_restricted"),
|
||||
editPost: null,
|
||||
|
||||
_isFastEditable: false,
|
||||
_displayFastEditInput: false,
|
||||
_fastEditInitalSelection: null,
|
||||
_fastEditNewSelection: null,
|
||||
_isSavingFastEdit: false,
|
||||
_canEditPost: false,
|
||||
|
||||
_isMouseDown: false,
|
||||
_reselected: false,
|
||||
|
@ -40,9 +59,18 @@ export default Component.extend({
|
|||
_hideButton() {
|
||||
this.quoteState.clear();
|
||||
this.set("visible", false);
|
||||
|
||||
this.set("_isFastEditable", false);
|
||||
this.set("_displayFastEditInput", false);
|
||||
this.set("_fastEditInitalSelection", null);
|
||||
this.set("_fastEditNewSelection", null);
|
||||
},
|
||||
|
||||
_selectionChanged() {
|
||||
if (this._displayFastEditInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const quoteState = this.quoteState;
|
||||
|
||||
const selection = window.getSelection();
|
||||
|
@ -104,6 +132,33 @@ export default Component.extend({
|
|||
quoteState.selected(postId, _selectedText, opts);
|
||||
this.set("visible", quoteState.buffer.length > 0);
|
||||
|
||||
if (this.siteSettings.enable_fast_edit) {
|
||||
this.set(
|
||||
"_canEditPost",
|
||||
this.topic.postStream.findLoadedPost(postId)?.can_edit
|
||||
);
|
||||
|
||||
// if we have a linebreak, the selection is probably too complex to be handled
|
||||
// by fast edit, so ignore it
|
||||
// if the selection is present multiple times, we also consider it too complex
|
||||
// and ignore it, note this specific case could probably be handled in the future
|
||||
const regexp = new RegExp(regexSafeStr(quoteState.buffer), "gi");
|
||||
const matches = postBody.match(regexp);
|
||||
if (
|
||||
quoteState.buffer.length < 1 ||
|
||||
quoteState.buffer.match(/\n/g) ||
|
||||
matches?.length > 1
|
||||
) {
|
||||
this.set("_isFastEditable", false);
|
||||
this.set("_fastEditInitalSelection", null);
|
||||
this.set("_fastEditNewSelection", null);
|
||||
} else if (matches?.length === 1) {
|
||||
this.set("_isFastEditable", true);
|
||||
this.set("_fastEditInitalSelection", quoteState.buffer);
|
||||
this.set("_fastEditNewSelection", quoteState.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// avoid hard loops in quote selection unconditionally
|
||||
// this can happen if you triple click text in firefox
|
||||
if (this._prevSelection === _selectedText) {
|
||||
|
@ -192,6 +247,12 @@ export default Component.extend({
|
|||
this._prevSelection = null;
|
||||
this._isMouseDown = true;
|
||||
this._reselected = false;
|
||||
|
||||
// prevents fast-edit input event to trigger mousedown
|
||||
if (e.target.classList.contains("fast-edit-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
$(e.target).closest(".quote-button, .create, .share, .reply-new")
|
||||
.length === 0
|
||||
|
@ -199,7 +260,12 @@ export default Component.extend({
|
|||
this._hideButton();
|
||||
}
|
||||
})
|
||||
.on("mouseup.quote-button", () => {
|
||||
.on("mouseup.quote-button", (e) => {
|
||||
// prevents fast-edit input event to trigger mouseup
|
||||
if (e.target.classList.contains("fast-edit-input")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._prevSelection = null;
|
||||
this._isMouseDown = false;
|
||||
onSelectionChanged();
|
||||
|
@ -264,11 +330,56 @@ export default Component.extend({
|
|||
);
|
||||
},
|
||||
|
||||
_saveFastEditDisabled: propertyEqual(
|
||||
"_fastEditInitalSelection",
|
||||
"_fastEditNewSelection"
|
||||
),
|
||||
|
||||
@action
|
||||
insertQuote() {
|
||||
this.attrs.selectText().then(() => this._hideButton());
|
||||
},
|
||||
|
||||
@action
|
||||
_toggleFastEditForm() {
|
||||
if (this._isFastEditable) {
|
||||
this.toggleProperty("_displayFastEditInput");
|
||||
|
||||
schedule("afterRender", () => {
|
||||
document.querySelector("#fast-edit-input")?.focus();
|
||||
});
|
||||
} else {
|
||||
const postId = this.quoteState.postId;
|
||||
const postModel = this.topic.postStream.findLoadedPost(postId);
|
||||
this?.editPost(postModel);
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
_saveFastEdit() {
|
||||
const postId = this.quoteState?.postId;
|
||||
const postModel = this.topic.postStream.findLoadedPost(postId);
|
||||
|
||||
this.set("_isSavingFastEdit", true);
|
||||
|
||||
return ajax(`/posts/${postModel.id}`, { type: "GET", cache: false })
|
||||
.then((result) => {
|
||||
const newRaw = result.raw.replace(
|
||||
fixQuotes(this._fastEditInitalSelection),
|
||||
fixQuotes(this._fastEditNewSelection)
|
||||
);
|
||||
|
||||
postModel
|
||||
.save({ raw: newRaw })
|
||||
.catch(popupAjaxError)
|
||||
.finally(() => {
|
||||
this.set("_isSavingFastEdit", false);
|
||||
this._hideButton();
|
||||
});
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
|
||||
@action
|
||||
share(source) {
|
||||
Sharing.shareSource(source, {
|
||||
|
|
|
@ -1,31 +1,64 @@
|
|||
{{#if embedQuoteButton}}
|
||||
{{d-button
|
||||
class="btn-flat insert-quote"
|
||||
action=(action "insertQuote")
|
||||
icon="quote-left"
|
||||
label="post.quote_reply"}}
|
||||
{{/if}}
|
||||
<div class="buttons">
|
||||
{{#if embedQuoteButton}}
|
||||
{{d-button
|
||||
class="btn-flat insert-quote"
|
||||
action=(action "insertQuote")
|
||||
icon="quote-left"
|
||||
label="post.quote_reply"}}
|
||||
{{/if}}
|
||||
|
||||
{{#if quoteSharingEnabled}}
|
||||
<span class="quote-sharing">
|
||||
{{#if quoteSharingShowLabel}}
|
||||
{{d-button
|
||||
icon="share"
|
||||
label="post.quote_share"
|
||||
class="btn-flat quote-share-label"}}
|
||||
{{/if}}
|
||||
|
||||
<span class="quote-share-buttons">
|
||||
{{#each quoteSharingSources as |source|}}
|
||||
{{#if quoteSharingEnabled}}
|
||||
<span class="quote-sharing">
|
||||
{{#if quoteSharingShowLabel}}
|
||||
{{d-button
|
||||
class="btn-flat"
|
||||
action=(action "share" source)
|
||||
translatedTitle=source.title
|
||||
icon=source.icon}}
|
||||
{{/each}}
|
||||
{{plugin-outlet name="quote-share-buttons-after" tagName=""}}
|
||||
</span>
|
||||
</span>
|
||||
{{/if}}
|
||||
icon="share"
|
||||
label="post.quote_share"
|
||||
class="btn-flat quote-share-label"}}
|
||||
{{/if}}
|
||||
|
||||
{{plugin-outlet name="quote-button-after" tagName=""}}
|
||||
<span class="quote-share-buttons">
|
||||
{{#each quoteSharingSources as |source|}}
|
||||
{{d-button
|
||||
class="btn-flat"
|
||||
action=(action "share" source)
|
||||
translatedTitle=source.title
|
||||
icon=source.icon}}
|
||||
{{/each}}
|
||||
{{plugin-outlet name="quote-share-buttons-after" tagName=""}}
|
||||
</span>
|
||||
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if siteSettings.enable_fast_edit}}
|
||||
{{#if _canEditPost}}
|
||||
{{d-button
|
||||
icon="pencil-alt"
|
||||
action=(action "_toggleFastEditForm")
|
||||
label="post.quote_edit"
|
||||
class="btn-flat quote-edit-label"
|
||||
}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="extra">
|
||||
{{#if siteSettings.enable_fast_edit}}
|
||||
{{#if _displayFastEditInput}}
|
||||
<div class="fast-edit-container">
|
||||
{{textarea
|
||||
id="fast-edit-input"
|
||||
value=_fastEditNewSelection
|
||||
}}
|
||||
{{d-button
|
||||
action=(action "_saveFastEdit")
|
||||
class="btn-default btn-primary save-fast-edit"
|
||||
label="save"
|
||||
disabled=_saveFastEditDisabled
|
||||
isLoading=_isSavingFastEdit
|
||||
}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{plugin-outlet name="quote-button-after" tagName=""}}
|
||||
</div>
|
||||
|
|
|
@ -404,5 +404,12 @@
|
|||
|
||||
{{share-popup topic=model replyAsNewTopic=(action "replyAsNewTopic")}}
|
||||
|
||||
{{quote-button quoteState=quoteState selectText=(action "selectText") topic=model composerVisible=composer.visible}}
|
||||
{{quote-button
|
||||
quoteState=quoteState
|
||||
selectText=(action "selectText")
|
||||
editPost=(action "editPost")
|
||||
topic=model
|
||||
composerVisible=composer.visible
|
||||
|
||||
}}
|
||||
{{/discourse-topic}}
|
||||
|
|
|
@ -378,9 +378,37 @@ aside.quote {
|
|||
z-index: z("dropdown");
|
||||
opacity: 0.9;
|
||||
background-color: var(--secondary-high);
|
||||
flex-direction: column;
|
||||
|
||||
&.visible {
|
||||
display: inline-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.extra {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fast-edit-container {
|
||||
display: flex;
|
||||
padding: 0.25em;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
#fast-edit-input {
|
||||
margin: 0;
|
||||
width: 300px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.save-fast-edit {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.btn,
|
||||
|
|
|
@ -2973,6 +2973,7 @@ en:
|
|||
|
||||
post:
|
||||
quote_reply: "Quote"
|
||||
quote_edit: "Edit"
|
||||
quote_share: "Share"
|
||||
edit_reason: "Reason: "
|
||||
post_number: "post %{number}"
|
||||
|
|
|
@ -2212,6 +2212,7 @@ en:
|
|||
watched_words_regular_expressions: "Watched words are regular expressions."
|
||||
|
||||
enable_diffhtml_preview: "Experimental feature which uses diffHTML to sync preview instead of full re-render"
|
||||
enable_fast_edit: "Experimental feature which enables small selection of a post text to be edited inline."
|
||||
|
||||
old_post_notice_days: "Days before post notice becomes old"
|
||||
new_user_notice_tl: "Minimum trust level required to see new user post notices."
|
||||
|
|
|
@ -1010,6 +1010,9 @@ posting:
|
|||
enable_diffhtml_preview:
|
||||
default: false
|
||||
client: true
|
||||
enable_fast_edit:
|
||||
default: false
|
||||
client: true
|
||||
old_post_notice_days:
|
||||
default: 14
|
||||
max: 36500
|
||||
|
|
Loading…
Reference in New Issue
Block a user