Merge pull request #324 from writeas/fix-unsynced-edits
Show warning in editor when local draft is out of date
This commit is contained in:
commit
ede68d86a7
|
@ -361,6 +361,24 @@ body#pad {
|
|||
z-index: 10;
|
||||
}
|
||||
|
||||
body#pad .alert {
|
||||
position: fixed;
|
||||
bottom: 0.25em;
|
||||
left: 2em;
|
||||
right: 2em;
|
||||
font-size: 1.1em;
|
||||
|
||||
&#edited-elsewhere {
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-height: 500px) {
|
||||
body#pad {
|
||||
textarea {
|
||||
|
@ -425,6 +443,10 @@ body#pad {
|
|||
padding-left: 10%;
|
||||
padding-right: 10%;
|
||||
}
|
||||
.alert {
|
||||
left: 10%;
|
||||
right: 10%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 60em) {
|
||||
|
@ -433,6 +455,10 @@ body#pad {
|
|||
padding-left: 15%;
|
||||
padding-right: 15%;
|
||||
}
|
||||
.alert {
|
||||
left: 15%;
|
||||
right: 15%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 70em) {
|
||||
|
@ -441,6 +467,10 @@ body#pad {
|
|||
padding-left: 20%;
|
||||
padding-right: 20%;
|
||||
}
|
||||
.alert {
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 85em) {
|
||||
|
@ -449,6 +479,10 @@ body#pad {
|
|||
padding-left: 25%;
|
||||
padding-right: 25%;
|
||||
}
|
||||
.alert {
|
||||
left: 25%;
|
||||
right: 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media all and (min-width: 105em) {
|
||||
|
@ -457,6 +491,10 @@ body#pad {
|
|||
padding-left: 30%;
|
||||
padding-right: 30%;
|
||||
}
|
||||
.alert {
|
||||
left: 30%;
|
||||
right: 30%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
|
|
21
posts.go
21
posts.go
|
@ -135,6 +135,7 @@ type (
|
|||
Views int64
|
||||
Font string
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
IsRTL sql.NullBool
|
||||
Language sql.NullString
|
||||
OwnerID int64
|
||||
|
@ -1240,9 +1241,9 @@ func getRawPost(app *App, friendlyID string) *RawPost {
|
|||
var isRTL sql.NullBool
|
||||
var lang sql.NullString
|
||||
var ownerID sql.NullInt64
|
||||
var created time.Time
|
||||
var created, updated time.Time
|
||||
|
||||
err := app.db.QueryRow("SELECT title, content, text_appearance, language, rtl, created, owner_id FROM posts WHERE id = ?", friendlyID).Scan(&title, &content, &font, &lang, &isRTL, &created, &ownerID)
|
||||
err := app.db.QueryRow("SELECT title, content, text_appearance, language, rtl, created, updated, owner_id FROM posts WHERE id = ?", friendlyID).Scan(&title, &content, &font, &lang, &isRTL, &created, &updated, &ownerID)
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return &RawPost{Content: "", Found: false, Gone: false}
|
||||
|
@ -1250,7 +1251,7 @@ func getRawPost(app *App, friendlyID string) *RawPost {
|
|||
return &RawPost{Content: "", Found: true, Gone: false}
|
||||
}
|
||||
|
||||
return &RawPost{Title: title, Content: content, Font: font, Created: created, IsRTL: isRTL, Language: lang, OwnerID: ownerID.Int64, Found: true, Gone: content == ""}
|
||||
return &RawPost{Title: title, Content: content, Font: font, Created: created, Updated: updated, IsRTL: isRTL, Language: lang, OwnerID: ownerID.Int64, Found: true, Gone: content == ""}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1259,15 +1260,15 @@ func getRawCollectionPost(app *App, slug, collAlias string) *RawPost {
|
|||
var id, title, content, font string
|
||||
var isRTL sql.NullBool
|
||||
var lang sql.NullString
|
||||
var created time.Time
|
||||
var created, updated time.Time
|
||||
var ownerID null.Int
|
||||
var views int64
|
||||
var err error
|
||||
|
||||
if app.cfg.App.SingleUser {
|
||||
err = app.db.QueryRow("SELECT id, title, content, text_appearance, language, rtl, view_count, created, owner_id FROM posts WHERE slug = ? AND collection_id = 1", slug).Scan(&id, &title, &content, &font, &lang, &isRTL, &views, &created, &ownerID)
|
||||
err = app.db.QueryRow("SELECT id, title, content, text_appearance, language, rtl, view_count, created, updated, owner_id FROM posts WHERE slug = ? AND collection_id = 1", slug).Scan(&id, &title, &content, &font, &lang, &isRTL, &views, &created, &updated, &ownerID)
|
||||
} else {
|
||||
err = app.db.QueryRow("SELECT id, title, content, text_appearance, language, rtl, view_count, created, owner_id FROM posts WHERE slug = ? AND collection_id = (SELECT id FROM collections WHERE alias = ?)", slug, collAlias).Scan(&id, &title, &content, &font, &lang, &isRTL, &views, &created, &ownerID)
|
||||
err = app.db.QueryRow("SELECT id, title, content, text_appearance, language, rtl, view_count, created, updated, owner_id FROM posts WHERE slug = ? AND collection_id = (SELECT id FROM collections WHERE alias = ?)", slug, collAlias).Scan(&id, &title, &content, &font, &lang, &isRTL, &views, &created, &updated, &ownerID)
|
||||
}
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
|
@ -1283,6 +1284,7 @@ func getRawCollectionPost(app *App, slug, collAlias string) *RawPost {
|
|||
Content: content,
|
||||
Font: font,
|
||||
Created: created,
|
||||
Updated: updated,
|
||||
IsRTL: isRTL,
|
||||
Language: lang,
|
||||
OwnerID: ownerID.Int64,
|
||||
|
@ -1543,6 +1545,13 @@ func (rp *RawPost) Created8601() string {
|
|||
return rp.Created.Format("2006-01-02T15:04:05Z")
|
||||
}
|
||||
|
||||
func (rp *RawPost) Updated8601() string {
|
||||
if rp.Updated.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return rp.Updated.Format("2006-01-02T15:04:05Z")
|
||||
}
|
||||
|
||||
var imageURLRegex = regexp.MustCompile(`(?i)[^ ]+\.(gif|png|jpg|jpeg|image)$`)
|
||||
|
||||
func (p *Post) extractImages() {
|
||||
|
|
|
@ -116,13 +116,27 @@ var H = {
|
|||
save: function($el, key) {
|
||||
localStorage.setItem(key, $el.el.value);
|
||||
},
|
||||
load: function($el, key, onlyLoadPopulated) {
|
||||
load: function($el, key, onlyLoadPopulated, postUpdated) {
|
||||
var val = localStorage.getItem(key);
|
||||
if (onlyLoadPopulated && val == null) {
|
||||
// Do nothing
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
$el.el.value = val;
|
||||
if (postUpdated != null) {
|
||||
var lastLocalPublishStr = localStorage.getItem(key+'-published');
|
||||
if (lastLocalPublishStr != null && lastLocalPublishStr != '') {
|
||||
try {
|
||||
var lastLocalPublish = new Date(lastLocalPublishStr);
|
||||
if (postUpdated > lastLocalPublish) {
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("unable to parse draft updated time");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
set: function(key, value) {
|
||||
localStorage.setItem(key, value);
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
<textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
|
||||
|
||||
{{end}}{{.Post.Content}}</textarea>
|
||||
|
||||
<div class="alert success hidden" id="edited-elsewhere">This post has been updated elsewhere since you last published! <a href="#" id="erase-edit">Delete draft and reload</a>.</div>
|
||||
|
||||
<header id="tools">
|
||||
<div id="clip">
|
||||
|
@ -36,6 +38,7 @@
|
|||
<script>
|
||||
var $writer = H.getEl('writer');
|
||||
var $btnPublish = H.getEl('publish');
|
||||
var $btnEraseEdit = H.getEl('edited-elsewhere');
|
||||
var $wc = H.getEl("wc");
|
||||
var updateWordCount = function() {
|
||||
var words = 0;
|
||||
|
@ -58,7 +61,17 @@
|
|||
};
|
||||
{{if .Post.Id}}var draftDoc = 'draft{{.Post.Id}}';
|
||||
var origDoc = '{{.Post.Content}}';{{else}}var draftDoc = 'lastDoc';{{end}}
|
||||
H.load($writer, draftDoc, true);
|
||||
var updatedStr = '{{.Post.Updated8601}}';
|
||||
var updated = null;
|
||||
if (updatedStr != '') {
|
||||
updated = new Date(updatedStr);
|
||||
}
|
||||
var ok = H.load($writer, draftDoc, true, updated);
|
||||
if (!ok) {
|
||||
// Show "edited elsewhere" warning
|
||||
$btnEraseEdit.el.classList.remove('hidden');
|
||||
}
|
||||
var defaultTimeSet = false;
|
||||
updateWordCount();
|
||||
|
||||
var typingTimer;
|
||||
|
@ -130,6 +143,7 @@
|
|||
data = JSON.parse(http.responseText);
|
||||
id = data.data.id;
|
||||
nextURL = '{{if .SingleUser}}/d{{end}}/'+id;
|
||||
localStorage.setItem('draft'+id+'-published', new Date().toISOString());
|
||||
|
||||
{{ if not .Post.Id }}
|
||||
// Post created
|
||||
|
@ -198,6 +212,13 @@
|
|||
publish(content, selectedFont);
|
||||
}
|
||||
});
|
||||
H.getEl('erase-edit').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
H.remove(draftDoc);
|
||||
H.remove(draftDoc+'-published');
|
||||
justPublished = true; // Block auto-save
|
||||
location.reload();
|
||||
});
|
||||
|
||||
WebFontConfig = {
|
||||
custom: { families: [ 'Lora:400,700:latin' ], urls: [ '/css/fonts.css' ] }
|
||||
|
@ -207,12 +228,20 @@
|
|||
var doneTyping = function() {
|
||||
if (draftDoc == 'lastDoc' || $writer.el.value != origDoc) {
|
||||
H.save($writer, draftDoc);
|
||||
if (!defaultTimeSet) {
|
||||
var lastLocalPublishStr = localStorage.getItem(draftDoc+'-published');
|
||||
if (lastLocalPublishStr == null || lastLocalPublishStr == '') {
|
||||
localStorage.setItem(draftDoc+'-published', updatedStr);
|
||||
}
|
||||
defaultTimeSet = true;
|
||||
}
|
||||
updateWordCount();
|
||||
}
|
||||
};
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
if (draftDoc != 'lastDoc' && $writer.el.value == origDoc) {
|
||||
H.remove(draftDoc);
|
||||
H.remove(draftDoc+'-published');
|
||||
} else if (!justPublished) {
|
||||
doneTyping();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
<textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
|
||||
|
||||
{{end}}{{.Post.Content}}</textarea>
|
||||
|
||||
<div class="alert success hidden" id="edited-elsewhere">This post has been updated elsewhere since you last published! <a href="#" id="erase-edit">Delete draft and reload</a>.</div>
|
||||
|
||||
<header id="tools">
|
||||
<div id="clip">
|
||||
|
@ -88,6 +90,7 @@
|
|||
}
|
||||
var $writer = H.getEl('writer');
|
||||
var $btnPublish = H.getEl('publish');
|
||||
var $btnEraseEdit = H.getEl('edited-elsewhere');
|
||||
var $wc = H.getEl("wc");
|
||||
var updateWordCount = function() {
|
||||
var words = 0;
|
||||
|
@ -110,7 +113,17 @@
|
|||
};
|
||||
{{if .Post.Id}}var draftDoc = 'draft{{.Post.Id}}';
|
||||
var origDoc = '{{.Post.Content}}';{{else}}var draftDoc = 'lastDoc';{{end}}
|
||||
H.load($writer, draftDoc, true);
|
||||
var updatedStr = '{{.Post.Updated8601}}';
|
||||
var updated = null;
|
||||
if (updatedStr != '') {
|
||||
updated = new Date(updatedStr);
|
||||
}
|
||||
var ok = H.load($writer, draftDoc, true, updated);
|
||||
if (!ok) {
|
||||
// Show "edited elsewhere" warning
|
||||
$btnEraseEdit.el.classList.remove('hidden');
|
||||
}
|
||||
var defaultTimeSet = false;
|
||||
updateWordCount();
|
||||
|
||||
var typingTimer;
|
||||
|
@ -190,6 +203,7 @@
|
|||
data = JSON.parse(http.responseText);
|
||||
id = data.data.id;
|
||||
nextURL = '{{if .SingleUser}}/d{{end}}/'+id;
|
||||
localStorage.setItem('draft'+id+'-published', new Date().toISOString());
|
||||
|
||||
{{ if not .Post.Id }}
|
||||
// Post created
|
||||
|
@ -258,6 +272,13 @@
|
|||
publish(content, selectedFont);
|
||||
}
|
||||
});
|
||||
H.getEl('erase-edit').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
H.remove(draftDoc);
|
||||
H.remove(draftDoc+'-published');
|
||||
justPublished = true; // Block auto-save
|
||||
location.reload();
|
||||
});
|
||||
|
||||
H.getEl('toggle-theme').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
@ -338,12 +359,20 @@
|
|||
var doneTyping = function() {
|
||||
if (draftDoc == 'lastDoc' || $writer.el.value != origDoc) {
|
||||
H.save($writer, draftDoc);
|
||||
if (!defaultTimeSet) {
|
||||
var lastLocalPublishStr = localStorage.getItem(draftDoc+'-published');
|
||||
if (lastLocalPublishStr == null || lastLocalPublishStr == '') {
|
||||
localStorage.setItem(draftDoc+'-published', updatedStr);
|
||||
}
|
||||
defaultTimeSet = true;
|
||||
}
|
||||
updateWordCount();
|
||||
}
|
||||
};
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
if (draftDoc != 'lastDoc' && $writer.el.value == origDoc) {
|
||||
H.remove(draftDoc);
|
||||
H.remove(draftDoc+'-published');
|
||||
} else if (!justPublished) {
|
||||
doneTyping();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue