Merge branch 'history' into staging
commit
08ba54b5cb
|
@ -165,7 +165,7 @@
|
|||
margin-bottom: @alertify_padding-base;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
:last-child {
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@
|
|||
background-color: @alertify-light-bg;
|
||||
}
|
||||
&.disabled {
|
||||
color: #949494;
|
||||
color: @colortheme_alertify-cancel-border;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&:not(.alertify-tabs-active) {
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 0;
|
||||
transition: none;
|
||||
|
||||
.fa, .cptools {
|
||||
margin-right: 0.2em;
|
||||
|
@ -160,6 +161,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.btn-light {
|
||||
border-color: @cryptpad_text_col;
|
||||
color: @cryptpad_text_col;
|
||||
background-color: transparent;
|
||||
&:hover, &:hover, &:focus {
|
||||
background-color: fade(@cryptpad_text_col, 25%);
|
||||
}
|
||||
}
|
||||
|
||||
&.cancel, &.btn-cancel {
|
||||
border-color: @colortheme_alertify-cancel-border;
|
||||
color: @colortheme_alertify-cancel-border;
|
||||
|
|
|
@ -141,6 +141,69 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.cp-snapshots-modal {
|
||||
& > input:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
.cp-snapshots-container {
|
||||
@snapshot_spacing: 10px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
color: @cryptpad_text_col;
|
||||
margin-bottom: @snapshot_spacing;
|
||||
max-height: 245px;
|
||||
overflow: auto;
|
||||
outline: none;
|
||||
.cp-snapshot-spinner {
|
||||
min-height: 90px;
|
||||
text-align: center;
|
||||
}
|
||||
.cp-snapshot-element {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
outline: none;
|
||||
& > i {
|
||||
margin-left: @snapshot_spacing;
|
||||
text-align: center;
|
||||
}
|
||||
.cp-snapshot-title {
|
||||
margin-left: @snapshot_spacing;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.cp-snapshot-time {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.cp-snapshot-buttons {
|
||||
margin-left: @snapshot_spacing;
|
||||
display: none;
|
||||
align-items: flex-start;
|
||||
margin-bottom: -3px;
|
||||
.cp-button-confirm {
|
||||
margin-right: @snapshot_spacing;
|
||||
}
|
||||
button {
|
||||
margin-right: @snapshot_spacing;
|
||||
}
|
||||
}
|
||||
&:hover, &:focus, &:focus-within {
|
||||
.cp-snapshot-buttons {
|
||||
display: flex;
|
||||
}
|
||||
background-color: #DDD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mediatag preview
|
||||
#cp-mediatag-preview-modal {
|
||||
.cp-modal {
|
||||
|
|
|
@ -5,92 +5,256 @@
|
|||
}
|
||||
& {
|
||||
.cp-toolbar-history {
|
||||
|
||||
@history_lineBg: #FFFFFF;
|
||||
@history_userBg1: #DDD;
|
||||
@history_userBg2: #BBB;
|
||||
@pos-color: @cryptpad_text_col;
|
||||
@fill-width: 40px;
|
||||
|
||||
display: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
padding: 10px 0 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: @cryptpad_text_col;
|
||||
* {
|
||||
font: @colortheme_app-font;
|
||||
}
|
||||
.cp-history-filler {
|
||||
flex: 1;
|
||||
}
|
||||
.cp-toolbar-history-close,
|
||||
.cp-toolbar-history-revert {
|
||||
background: white;
|
||||
color: black;
|
||||
//margin-top: 5px;
|
||||
&:hover {
|
||||
background-color: #e6e6e6;
|
||||
|
||||
|
||||
@media screen and (max-width: 870px) {
|
||||
flex-flow: column;
|
||||
.cp-toolbar-history-actions {
|
||||
width: 100%;
|
||||
.cp-history-actions-first {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
.cp-toolbar-history-timeline {
|
||||
width: ~"calc(100% - 20px)";
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
}
|
||||
.cp-toolbar-history-loadmore {
|
||||
height: 100%;
|
||||
color: black;
|
||||
width: 25px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
||||
@media screen and (max-height: 500px) {
|
||||
padding-top: 0px;
|
||||
.cp-history-timeline-line {
|
||||
display: none !important;
|
||||
}
|
||||
.cp-toolbar-history-timeline {
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.cp-history-timeline-actions {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.cp-history-init {
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
}
|
||||
.cp-toolbar-history-version {
|
||||
position: absolute;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: black;
|
||||
}
|
||||
.cp-toolbar-history-goto {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
|
||||
.cp-toolbar-history-timeline {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
flex-basis: 80%;
|
||||
min-width: 0;
|
||||
max-width: 600px;
|
||||
input { width: 75px; }
|
||||
}
|
||||
.cp-toolbar-history-goto-input {
|
||||
padding-left: 5px;
|
||||
margin-left: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cp-toolbar-history-bar {
|
||||
width: 100%;
|
||||
background: white;
|
||||
height: 25px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
.cp-toolbar-history-pos-container {
|
||||
width: ~"calc(100% - 2px)";
|
||||
height: 25px;
|
||||
position: relative;
|
||||
}
|
||||
@pos-color: #55FF55;
|
||||
.cp-toolbar-history-pos {
|
||||
width: 2px;
|
||||
height: 25px;
|
||||
background: @pos-color;
|
||||
&:after {
|
||||
content: '';
|
||||
border: 6px solid transparent;
|
||||
border-top-color: @pos-color;
|
||||
margin-left: -5px;
|
||||
margin-left: 10px;
|
||||
margin-right: @fill-width;
|
||||
.cp-history-timeline-time {
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
button {
|
||||
color: @cryptpad_text_col;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
&:hover {
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
.cp-toolbar-history-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 39px;
|
||||
align-self: baseline;
|
||||
margin-right: 5px;
|
||||
.cp-history-actions-first {
|
||||
margin-right: @fill-width;
|
||||
}
|
||||
button {
|
||||
margin: 0 5px;
|
||||
border: 1px solid @cryptpad_text_col;
|
||||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.fa:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
&:disabled {
|
||||
cursor: not-allowed !important;
|
||||
opacity: 0.6;
|
||||
&:hover, &:active {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fa-spinner {
|
||||
font-size: 66px;
|
||||
|
||||
&.cp-history-drive {
|
||||
.cp-history-timeline-container {
|
||||
height: 20px !important;
|
||||
}
|
||||
.cp-history-timeline-users {
|
||||
display: none !important;
|
||||
}
|
||||
.cp-history-timeline-legend {
|
||||
display: none !important;
|
||||
}
|
||||
.cp-history-timeline-pos {
|
||||
height: 18px !important;
|
||||
}
|
||||
.cp-toolbar-history-loadmore {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
.cp-history-timeline-actions {
|
||||
margin-left: 21px !important;
|
||||
}
|
||||
}
|
||||
&.cp-smallpatch {
|
||||
.cp-history-snapshot {
|
||||
border: none !important;
|
||||
width: 2px !important;
|
||||
background: @pos-color;
|
||||
}
|
||||
.cp-history-timeline-pos {
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
width: 2px !important;
|
||||
background: @pos-color;
|
||||
&:before {
|
||||
left: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-history-timeline-line {
|
||||
display: flex;
|
||||
.cp-history-timeline-legend {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.cp-history-timeline-loadmore {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
button {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: @history_lineBg;
|
||||
margin-right: 1px;
|
||||
.fa-refresh {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-history-timeline-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background-color: @history_lineBg;
|
||||
height: 39px;
|
||||
}
|
||||
.cp-history-timeline-bar {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 1px;
|
||||
& > span {
|
||||
height: 18px;
|
||||
display: flex;
|
||||
}
|
||||
.cp-history-timeline-users {
|
||||
margin-bottom: 1px;
|
||||
.cp-history-bar-el {
|
||||
background-color: @history_userBg1;
|
||||
&:nth-child(2n) {
|
||||
background-color: @history_userBg2;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-history-timeline-user {
|
||||
.cp-history-bar-el {
|
||||
background-color: @history_userBg2;
|
||||
&:nth-child(2n) {
|
||||
background-color: @history_userBg1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-history-snapshots {
|
||||
position: absolute;
|
||||
left: 1px;
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
bottom: 1px;
|
||||
cursor: pointer;
|
||||
.cp-history-snapshot {
|
||||
position: absolute;
|
||||
border: 2px solid @cryptpad_text_col;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-history-timeline-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-left: 40px;
|
||||
button {
|
||||
width: 50px;
|
||||
.fa:first-child:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
&:disabled {
|
||||
cursor: not-allowed !important;
|
||||
opacity: 0.6;
|
||||
&:hover, &:active {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-history-timeline-next {
|
||||
button:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.cp-history-timeline-prev {
|
||||
button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-history-timeline-pos {
|
||||
//width: 2px;
|
||||
border: 2px solid @cryptpad_text_col;
|
||||
height: 37px;
|
||||
//background: @pos-color;
|
||||
position: absolute;
|
||||
&:before {
|
||||
top: -17px;
|
||||
font-size: 24px;
|
||||
position: absolute;
|
||||
left: ~"calc(50% - 6px)";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -483,6 +483,7 @@
|
|||
height: @toolbar_line-height;
|
||||
box-sizing: border-box;
|
||||
line-height: @toolbar_line-height;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.cp-toolbar-title-readonly {
|
||||
font-size: 14px;
|
||||
|
@ -490,7 +491,7 @@
|
|||
.cp-toolbar-title-value {
|
||||
padding: 5px;
|
||||
line-height: @toolbar_line-height - 10px;
|
||||
border: 0;
|
||||
//border: 0;
|
||||
}
|
||||
.cp-toolbar-title-edit, .cp-toolbar-title-save {
|
||||
box-sizing: border-box;
|
||||
|
@ -663,6 +664,20 @@
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
&.cp-toolbar-unsync {
|
||||
.cp-toolbar-title-edit, .cp-toolbar-title-save {
|
||||
display: none !important;
|
||||
}
|
||||
.cp-toolbar-title-unsync {
|
||||
display: inline-flex;
|
||||
}
|
||||
.cp-toolbar-title-editable, .cp-toolbar-title-edit {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
.cp-toolbar-title-unsync {
|
||||
display: none;
|
||||
}
|
||||
.cp-toolbar-title-hoverable {
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
|
@ -886,11 +901,45 @@
|
|||
}
|
||||
}
|
||||
|
||||
.cp-toolbar-history {
|
||||
.cp-toolbar-history, .cp-toolbar-snapshots {
|
||||
background-color: @toolbar-bg-color-light;
|
||||
background-color: var(--toolbar-bg-color-light);
|
||||
color: @cryptpad_text_col;
|
||||
}
|
||||
.cp-toolbar-snapshots {
|
||||
display: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.cp-toolbar-snapshots-info {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
i {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: @browser_media-medium-screen) {
|
||||
flex-flow: column;
|
||||
.cp-toolbar-snapshots-info {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
.cp-toolbar-snapshots-actions {
|
||||
button {
|
||||
margin: 0 5px;
|
||||
border: 1px solid @cryptpad_text_col;
|
||||
text-transform: uppercase;
|
||||
i:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-toolbar-bottom {
|
||||
background-color: @toolbar-bg-color-light;
|
||||
background-color: var(--toolbar-bg-color-light);
|
||||
|
|
|
@ -128,7 +128,6 @@ define([
|
|||
};
|
||||
var mkHelpMenu = function (framework) {
|
||||
var $codeMirrorContainer = $('#cp-app-code-container');
|
||||
$codeMirrorContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning());
|
||||
var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'code']);
|
||||
$codeMirrorContainer.prepend(helpMenu.menu);
|
||||
|
||||
|
|
|
@ -169,6 +169,17 @@ Version 1
|
|||
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
|
||||
*/
|
||||
|
||||
var getVersionHash = function (hashArr) {
|
||||
var k;
|
||||
// Check if we have a ownerKey for this pad
|
||||
hashArr.some(function (data) {
|
||||
if (/^hash=/.test(data)) {
|
||||
k = data.slice(5);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return k ? Crypto.b64AddSlashes(k) : '';
|
||||
};
|
||||
var getOwnerKey = function (hashArr) {
|
||||
var k;
|
||||
// Check if we have a ownerKey for this pad
|
||||
|
@ -190,6 +201,7 @@ Version 1
|
|||
parsed.password = options.indexOf('p') !== -1;
|
||||
parsed.present = options.indexOf('present') !== -1;
|
||||
parsed.embed = options.indexOf('embed') !== -1;
|
||||
parsed.versionHash = getVersionHash(options);
|
||||
parsed.ownerKey = getOwnerKey(options);
|
||||
};
|
||||
|
||||
|
@ -201,6 +213,7 @@ Version 1
|
|||
embed: parsed.embed,
|
||||
present: parsed.present,
|
||||
ownerKey: parsed.ownerKey,
|
||||
versionHash: parsed.versionHash,
|
||||
password: parsed.password
|
||||
};
|
||||
};
|
||||
|
@ -220,6 +233,10 @@ Version 1
|
|||
if (parsed.password || opts.password) { hash += 'p/'; }
|
||||
if (opts.embed) { hash += 'embed/'; }
|
||||
if (opts.present) { hash += 'present/'; }
|
||||
var versionHash = typeof(opts.versionHash) !== "undefined" ? opts.versionHash : parsed.versionHash;
|
||||
if (versionHash) {
|
||||
hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/';
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
|
|
|
@ -758,23 +758,16 @@ define([
|
|||
button = $('<span>');
|
||||
break;
|
||||
}
|
||||
var active = $(".cp-toolbar-history:visible").length !== 0;
|
||||
button = $('<button>', {
|
||||
title: active ? Messages.history_closeTitle : Messages.historyButton,
|
||||
title: Messages.historyButton,
|
||||
'class': "fa fa-history cp-toolbar-icon-history",
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.historyText));
|
||||
button.toggleClass("active", active);
|
||||
if (data.histConfig) {
|
||||
if (active) {
|
||||
button.click(function () { $(".cp-toolbar-history-close").trigger("click"); });
|
||||
}
|
||||
else {
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.on('click', function () {
|
||||
common.getHistory(data.histConfig);
|
||||
});
|
||||
}
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.on('click', function () {
|
||||
common.getHistory(data.histConfig);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'mediatag':
|
||||
|
@ -876,6 +869,21 @@ define([
|
|||
common.createNewPadModal();
|
||||
});
|
||||
break;
|
||||
case 'snapshots':
|
||||
button = $('<button>', {
|
||||
title: Messages.snapshots_button,
|
||||
'class': 'fa fa-camera cp-toolbar-icon-snapshots',
|
||||
}).append($('<span>', {'class': 'cp-toolbar-drawer-element'}).text(Messages.snapshots_button));
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
data = data || {};
|
||||
if (typeof(data.load) !== "function" || typeof(data.make) !== "function") {
|
||||
return;
|
||||
}
|
||||
UIElements.openSnapshotsModal(common, data.load, data.make, data.remove);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
data = data || {};
|
||||
var drawerCls = data.drawer === false ? '' : '.cp-toolbar-drawer-element';
|
||||
|
@ -3301,5 +3309,117 @@ define([
|
|||
return (pos.bottom < size) && (pos.y > 0);
|
||||
};
|
||||
|
||||
UIElements.openSnapshotsModal = function (common, load, make, remove) {
|
||||
var modal;
|
||||
var readOnly = common.getMetadataMgr().getPrivateData().readOnly;
|
||||
|
||||
var container = h('div.cp-snapshots-container', {tabindex:1});
|
||||
var $container = $(container);
|
||||
|
||||
var input = h('input', {
|
||||
tabindex: 1,
|
||||
placeholder: Messages.snapshots_placeholder
|
||||
});
|
||||
var $input = $(input);
|
||||
var content = h('div.cp-snapshots-modal', [
|
||||
h('h5', Messages.snapshots_button),
|
||||
container,
|
||||
readOnly ? undefined : h('label', Messages.snapshots_new),
|
||||
readOnly ? undefined : input
|
||||
]);
|
||||
|
||||
var refresh = function () {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var md = metadataMgr.getMetadata();
|
||||
var snapshots = md.snapshots || {};
|
||||
|
||||
var list = Object.keys(snapshots).sort(function (h1, h2) {
|
||||
var s1 = snapshots[h1];
|
||||
var s2 = snapshots[h2];
|
||||
return s1.time - s2.time;
|
||||
}).map(function (hash) {
|
||||
var s = snapshots[hash];
|
||||
|
||||
var openButton = h('button.cp-snapshot-view.btn.btn-light', {
|
||||
tabindex: 1,
|
||||
}, [
|
||||
h('i.fa.fa-eye'),
|
||||
h('span', Messages.snapshots_open)
|
||||
]);
|
||||
$(openButton).click(function () {
|
||||
load(hash, s);
|
||||
if (modal && modal.closeModal) {
|
||||
modal.closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
var deleteButton = h('button.cp-snapshot-delete.btn.btn-light', {
|
||||
tabindex: 1,
|
||||
}, [
|
||||
h('i.fa.fa-trash'),
|
||||
h('span', Messages.snapshots_delete)
|
||||
]);
|
||||
UI.confirmButton(deleteButton, {
|
||||
classes: 'btn-danger'
|
||||
}, function () {
|
||||
remove(hash, s);
|
||||
refresh();
|
||||
});
|
||||
|
||||
return h('span.cp-snapshot-element', {tabindex:1}, [
|
||||
h('i.fa.fa-camera'),
|
||||
h('span.cp-snapshot-title', [
|
||||
h('span', s.title),
|
||||
h('span.cp-snapshot-time', new Date(s.time).toLocaleString())
|
||||
]),
|
||||
h('span.cp-snapshot-buttons', [
|
||||
readOnly ? undefined : deleteButton,
|
||||
openButton,
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
$container.html('').append(list);
|
||||
setTimeout(function () {
|
||||
if (list.length) { return void $container.focus(); }
|
||||
$input.focus();
|
||||
});
|
||||
};
|
||||
refresh();
|
||||
|
||||
var buttons = [{
|
||||
className: 'cancel',
|
||||
name: Messages.filePicker_close,
|
||||
onClick: function () {},
|
||||
keys: [27],
|
||||
}];
|
||||
if (!readOnly) {
|
||||
buttons.push({
|
||||
className: 'primary',
|
||||
iconClass: '.fa.fa-camera',
|
||||
name: Messages.snapshots_new,
|
||||
onClick: function () {
|
||||
var val = $input.val();
|
||||
if (!val) { return true; }
|
||||
$container.html('').append(h('div.cp-snapshot-spinner'));
|
||||
var to = setTimeout(function () {
|
||||
UI.spinner($container.find('div')).get().show();
|
||||
});
|
||||
make(val, function (err) {
|
||||
clearTimeout(to);
|
||||
if (err) {
|
||||
return void UI.alert(Messages.snapshots_cantMake);
|
||||
}
|
||||
refresh();
|
||||
});
|
||||
return true;
|
||||
},
|
||||
keys: [],
|
||||
});
|
||||
}
|
||||
|
||||
modal = UI.openCustomModal(UI.dialog.customModal(content, {buttons: buttons }));
|
||||
};
|
||||
|
||||
return UIElements;
|
||||
});
|
||||
|
|
|
@ -924,6 +924,12 @@ define([
|
|||
// -1 ==> no timeout, we may receive the callback only when we reconnect
|
||||
postMessage("SEND_PAD_MSG", data, cb, { timeout: -1 });
|
||||
};
|
||||
pad.getLastHash = function (data, cb) {
|
||||
postMessage("GET_LAST_HASH", data, cb);
|
||||
};
|
||||
pad.getSnapshot = function (data, cb) {
|
||||
postMessage("GET_SNAPSHOT", data, cb);
|
||||
};
|
||||
pad.onReadyEvent = Util.mkEvent();
|
||||
pad.onMessageEvent = Util.mkEvent();
|
||||
pad.onJoinEvent = Util.mkEvent();
|
||||
|
|
|
@ -329,26 +329,29 @@ define([
|
|||
}
|
||||
|
||||
// warning about sharing links
|
||||
var localStore = window.cryptpadStore;
|
||||
var dismissButton = h('span.fa.fa-times');
|
||||
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
|
||||
{ style: 'display: none;' },
|
||||
[
|
||||
h('span.cp-inline-alert-text', Messages.share_linkWarning),
|
||||
dismissButton
|
||||
]);
|
||||
linkContent.push(shareLinkWarning);
|
||||
// when sharing a version hash, there is a similar warning and we want
|
||||
// to avoid alert fatigue
|
||||
if (!opts.versionHash) {
|
||||
var localStore = window.cryptpadStore;
|
||||
var dismissButton = h('span.fa.fa-times');
|
||||
var shareLinkWarning = h('div.alert.alert-warning.dismissable',
|
||||
{ style: 'display: none;' },
|
||||
[
|
||||
h('span.cp-inline-alert-text', Messages.share_linkWarning),
|
||||
dismissButton
|
||||
]);
|
||||
linkContent.push(shareLinkWarning);
|
||||
|
||||
localStore.get('hide-alert-shareLinkWarning', function (val) {
|
||||
if (val === '1') { return; }
|
||||
$(shareLinkWarning).css('display', 'flex');
|
||||
localStore.get('hide-alert-shareLinkWarning', function (val) {
|
||||
if (val === '1') { return; }
|
||||
$(shareLinkWarning).show();
|
||||
|
||||
$(dismissButton).on('click', function () {
|
||||
localStore.put('hide-alert-shareLinkWarning', '1');
|
||||
$(shareLinkWarning).remove();
|
||||
$(dismissButton).on('click', function () {
|
||||
localStore.put('hide-alert-shareLinkWarning', '1');
|
||||
$(shareLinkWarning).remove();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Burn after reading
|
||||
if (opts.barAlert) { linkContent.push(opts.barAlert.cloneNode(true)); }
|
||||
|
@ -362,9 +365,6 @@ define([
|
|||
}));
|
||||
});
|
||||
|
||||
//Messages.share_bar = "Generate link"; // XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
|
||||
Messages.share_bar = Messages.team_inviteLinkCreate; // XXX
|
||||
|
||||
var linkButtons = [
|
||||
makeCancelButton(),
|
||||
!opts.sharedFolder && {
|
||||
|
@ -462,7 +462,8 @@ define([
|
|||
var pathname = opts.pathname;
|
||||
var parsed = Hash.parsePadUrl(pathname);
|
||||
var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1;
|
||||
var canBAR = parsed.type !== 'drive';
|
||||
var versionHash = hashes.viewHash && opts.versionHash;
|
||||
var canBAR = parsed.type !== 'drive' && !versionHash;
|
||||
|
||||
var burnAfterReading = (hashes.viewHash && canBAR) ?
|
||||
UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, {
|
||||
|
@ -521,6 +522,12 @@ define([
|
|||
var embed = val.embed;
|
||||
var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present'));
|
||||
var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar'));
|
||||
if (versionHash) {
|
||||
edit = false;
|
||||
embed = false;
|
||||
present = false;
|
||||
burnAfterReading = false;
|
||||
}
|
||||
if (burnAfterReading && !opts.burnAfterReadingUrl) {
|
||||
if (cb) { // Called from the contacts tab, "share" button
|
||||
var barHref = origin + pathname + '#' + (hashes.viewHash || hashes.editHash);
|
||||
|
@ -535,7 +542,7 @@ define([
|
|||
var href = burnAfterReading ? opts.burnAfterReadingUrl
|
||||
: (origin + pathname + '#' + hash);
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
return origin + parsed.getUrl({embed: embed, present: present});
|
||||
return origin + parsed.getUrl({embed: embed, present: present, versionHash: versionHash});
|
||||
};
|
||||
opts.getEmbedValue = function () {
|
||||
var url = opts.getLinkValue({
|
||||
|
@ -545,7 +552,11 @@ define([
|
|||
};
|
||||
|
||||
// disable edit share options if you don't have edit rights
|
||||
if (!hashes.editHash) {
|
||||
if (versionHash) {
|
||||
$rights.find('#cp-share-editable-false').attr('checked', true);
|
||||
$rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true);
|
||||
$rights.find('#cp-share-editable-true').removeAttr('checked').attr('disabled', true);
|
||||
} else if (!hashes.editHash) {
|
||||
$rights.find('#cp-share-editable-false').attr('checked', true);
|
||||
$rights.find('#cp-share-editable-true').removeAttr('checked').attr('disabled', true);
|
||||
} else if (!hashes.viewHash) {
|
||||
|
@ -584,7 +595,9 @@ define([
|
|||
// Set default values
|
||||
common.getAttribute(['general', 'share'], function (err, val) {
|
||||
val = val || {};
|
||||
if (val.present && canPresent) {
|
||||
if (versionHash) {
|
||||
$rights.find('#cp-share-editable-false').prop('checked', true);
|
||||
} else if (val.present && canPresent) {
|
||||
$rights.find('#cp-share-editable-false').prop('checked', false);
|
||||
$rights.find('#cp-share-editable-true').prop('checked', false);
|
||||
$rights.find('#cp-share-present').prop('checked', true);
|
||||
|
@ -673,9 +686,21 @@ define([
|
|||
onHide: resetTab
|
||||
}];
|
||||
Modal.getModal(common, opts, tabs, function (err, modal) {
|
||||
$(modal).find('.cp-bar').hide();
|
||||
// Hide the burn-after-reading option by default
|
||||
var $modal = $(modal);
|
||||
$modal.find('.cp-bar').hide();
|
||||
|
||||
// Prepend the "rights" radio selection
|
||||
$(modal).find('.alertify-tabs-titles').after($rights);
|
||||
$modal.find('.alertify-tabs-titles').after($rights);
|
||||
|
||||
// Add the versionHash warning if needed
|
||||
if (opts.versionHash) {
|
||||
$rights.after(h('div.alert.alert-warning', [
|
||||
h('i.fa.fa-history'),
|
||||
UI.setHTML(h('span'), Messages.share_versionHash)
|
||||
]));
|
||||
}
|
||||
|
||||
// callback
|
||||
cb(err, modal);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/common/common-interface.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function ($, UI, h, Messages, nThen, ChainPad /* JsonOT */) {
|
||||
var Snapshots = {};
|
||||
|
||||
Snapshots.create = function (common, config) {
|
||||
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
|
||||
if (Snapshots.loading) { return void console.error("Snapshot is already being loaded..."); }
|
||||
Snapshots.loading = true;
|
||||
|
||||
var sframeChan = common.getSframeChannel();
|
||||
|
||||
var $toolbar = config.$toolbar;
|
||||
var $snap = $toolbar.find('.cp-toolbar-snapshots');
|
||||
var $bottom = $toolbar.find('.cp-toolbar-bottom');
|
||||
var $cke = $toolbar.find('.cke_toolbox_main');
|
||||
|
||||
$snap.html('').css('display', 'flex');
|
||||
$bottom.hide();
|
||||
$cke.hide();
|
||||
|
||||
var createChainPad = function () {
|
||||
return ChainPad.create({
|
||||
userName: 'snapshot',
|
||||
validateContent: function (content) {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log('Failed to parse, rejecting patch');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
initialState: '',
|
||||
logLevel: 0
|
||||
});
|
||||
};
|
||||
|
||||
var snapshot;
|
||||
var getData = function () {
|
||||
sframeChan.query("Q_GET_SNAPSHOT", {hash: config.hash}, function (err, obj) {
|
||||
if (err || (obj && obj.error)) { return void console.error(err || obj.error); }
|
||||
if (!Array.isArray(obj)) { return void console.error("invalid type"); }
|
||||
if (!obj.length) { return void console.error("Empty channel"); }
|
||||
var checkLast = obj[obj.length - 1].serverHash === config.hash;
|
||||
if (!checkLast) {
|
||||
$snap.find('.cp-toolbar-snapshots-close').click();
|
||||
return void UI.alert(Messages.snapshots_notFound);
|
||||
}
|
||||
|
||||
var messages = obj;
|
||||
var chainpad = createChainPad();
|
||||
messages.forEach(function (m) {
|
||||
chainpad.message(m);
|
||||
});
|
||||
snapshot = chainpad.getAuthDoc();
|
||||
config.applyVal(snapshot);
|
||||
chainpad.abort();
|
||||
});
|
||||
};
|
||||
|
||||
var display = function () {
|
||||
var data = config.data || {};
|
||||
|
||||
var actions = h('span.cp-toolbar-snapshots-actions');
|
||||
var $actions = $(actions);
|
||||
var content = [
|
||||
h('span.cp-toolbar-snapshots-info', [
|
||||
h('i.fa.fa-camera'),
|
||||
h('span.cp-toolbar-snapshots-title', data.title + ' - ' + new Date(data.time).toLocaleString()),
|
||||
]),
|
||||
actions
|
||||
];
|
||||
|
||||
if (!config.readOnly) {
|
||||
$(h('button.cp-toolbar-snapshots-restore', [
|
||||
h('i.fa.fa-check'),
|
||||
h('spap.cp-button-name', Messages.snapshots_restore)
|
||||
])).click(function () {
|
||||
var closed = config.close(true, snapshot);
|
||||
if (!closed) {
|
||||
return void UI.alert(Messages.snapshots_cantRestore);
|
||||
}
|
||||
$snap.hide();
|
||||
$bottom.show();
|
||||
$cke.show();
|
||||
Snapshots.loading = false;
|
||||
}).appendTo($actions);
|
||||
}
|
||||
|
||||
|
||||
$(h('button.cp-toolbar-snapshots-close', [
|
||||
h('i.fa.fa-times'),
|
||||
h('spap.cp-button-name', Messages.snapshots_close)
|
||||
])).click(function () {
|
||||
$snap.hide();
|
||||
$bottom.show();
|
||||
$cke.show();
|
||||
Snapshots.loading = false;
|
||||
config.close(false);
|
||||
}).appendTo($actions);
|
||||
|
||||
$snap.append(content);
|
||||
};
|
||||
|
||||
display();
|
||||
getData();
|
||||
};
|
||||
|
||||
return Snapshots;
|
||||
});
|
||||
|
||||
|
|
@ -14,6 +14,7 @@ define(['json.sortify'], function (Sortify) {
|
|||
var metadataLazyObj = UNINIT;
|
||||
var priv = {};
|
||||
var dirty = true;
|
||||
var history = false;
|
||||
var changeHandlers = [];
|
||||
var lazyChangeHandlers = [];
|
||||
var titleChangeHandlers = [];
|
||||
|
@ -60,7 +61,7 @@ define(['json.sortify'], function (Sortify) {
|
|||
var mdo = {};
|
||||
// We don't want to add our user data to the object multiple times.
|
||||
Object.keys(metadataObj.users).forEach(function (x) {
|
||||
if (members.indexOf(x) === -1) { return; }
|
||||
if (members.indexOf(x) === -1 && !history) { return; }
|
||||
mdo[x] = metadataObj.users[x];
|
||||
});
|
||||
if (!priv.readOnly) {
|
||||
|
@ -161,6 +162,9 @@ define(['json.sortify'], function (Sortify) {
|
|||
metadataLazyObj = JSON.parse(JSON.stringify(m));
|
||||
change(false);
|
||||
},
|
||||
refresh : function () {
|
||||
change(true);
|
||||
},
|
||||
updateTitle: function (t) {
|
||||
metadataObj.title = t;
|
||||
change(true);
|
||||
|
@ -207,6 +211,9 @@ define(['json.sortify'], function (Sortify) {
|
|||
if (isReady) { return void f(); }
|
||||
readyHandlers.push(f);
|
||||
},
|
||||
setHistory: function (bool) {
|
||||
history = bool;
|
||||
}
|
||||
});
|
||||
};
|
||||
return Object.freeze({ create: create });
|
||||
|
|
|
@ -1455,7 +1455,57 @@ define([
|
|||
|
||||
var channels = Store.channels = store.channels = {};
|
||||
|
||||
Store.getSnapshot = function (clientId, data, cb) {
|
||||
Store.getHistoryRange(clientId, {
|
||||
cpCount: 1,
|
||||
channel: data.channel,
|
||||
lastKnownHash: data.hash
|
||||
}, cb);
|
||||
};
|
||||
|
||||
var getVersionHash = function (clientId, data) {
|
||||
var validateKey;
|
||||
var fakeNetflux = Hash.createChannelId();
|
||||
nThen(function (waitFor) {
|
||||
Store.getPadMetadata(null, {
|
||||
channel: data.channel
|
||||
}, waitFor(function (md) {
|
||||
validateKey = md.validateKey;
|
||||
}));
|
||||
}).nThen(function () {
|
||||
Store.getHistoryRange(clientId, {
|
||||
cpCount: 1,
|
||||
channel: data.channel,
|
||||
lastKnownHash: data.versionHash
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
postMessage(clientId, "PAD_ERROR", obj.error);
|
||||
return;
|
||||
}
|
||||
postMessage(clientId, "PAD_CONNECT", {
|
||||
myID: fakeNetflux,
|
||||
id: data.channel,
|
||||
members: [fakeNetflux]
|
||||
});
|
||||
(obj.messages || []).forEach(function (data) {
|
||||
postMessage(clientId, "PAD_MESSAGE", {
|
||||
msg: data.msg,
|
||||
time: data.time,
|
||||
user: fakeNetflux.slice(0,16), // fake history keeper to avoid validate
|
||||
});
|
||||
});
|
||||
if (validateKey && store.messenger) {
|
||||
store.messenger.storeValidateKey(data.channel, validateKey);
|
||||
}
|
||||
postMessage(clientId, "PAD_READY");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Store.joinPad = function (clientId, data) {
|
||||
if (data.versionHash) {
|
||||
return void getVersionHash(clientId, data);
|
||||
}
|
||||
var isNew = typeof channels[data.channel] === "undefined";
|
||||
var channel = channels[data.channel] = channels[data.channel] || {
|
||||
queue: [],
|
||||
|
@ -1529,7 +1579,8 @@ define([
|
|||
}
|
||||
postMessage(clientId, "PAD_READY");
|
||||
},
|
||||
onMessage: function (m, user, validateKey, isCp) {
|
||||
onMessage: function (m, user, validateKey, isCp, hash) {
|
||||
channel.lastHash = hash;
|
||||
channel.pushHistory(m, isCp);
|
||||
channel.bcast("PAD_MESSAGE", {
|
||||
user: user,
|
||||
|
@ -1615,6 +1666,7 @@ define([
|
|||
return void cb({ error: err });
|
||||
}
|
||||
// Broadcast to other tabs
|
||||
channel.lastHash = msg.slice(0,64);
|
||||
channel.pushHistory(CpNetflux.removeCp(msg), /^cp\|/.test(msg));
|
||||
channel.bcast("PAD_MESSAGE", {
|
||||
user: wc.myID,
|
||||
|
@ -1750,6 +1802,15 @@ define([
|
|||
cb();
|
||||
};
|
||||
|
||||
Store.getLastHash = function (clientId, data, cb) {
|
||||
var chan = channels[data.channel];
|
||||
if (!chan) { return void cb({error: 'ENOCHAN'}); }
|
||||
if (!chan.lastHash) { return void cb({error: 'EINVAL'}); }
|
||||
cb({
|
||||
hash: chan.lastHash
|
||||
});
|
||||
};
|
||||
|
||||
// Delete a pad received with a burn after reading URL
|
||||
|
||||
var notifyOwnerPadRemoved = function (data, obj) {
|
||||
|
@ -2035,6 +2096,7 @@ define([
|
|||
}
|
||||
msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, '');
|
||||
msgs.push({
|
||||
serverHash: msg.slice(0,64),
|
||||
msg: msg,
|
||||
author: parsed[2][1],
|
||||
time: parsed[2][5]
|
||||
|
@ -2045,7 +2107,7 @@ define([
|
|||
network.on('message', onMsg);
|
||||
network.sendto(hk, JSON.stringify(['GET_HISTORY_RANGE', data.channel, {
|
||||
from: data.lastKnownHash,
|
||||
cpCount: 2,
|
||||
cpCount: data.cpCount || 2,
|
||||
txid: txid
|
||||
}]));
|
||||
};
|
||||
|
|
|
@ -86,6 +86,8 @@ define([
|
|||
GET_PAD_METADATA: Store.getPadMetadata,
|
||||
SET_PAD_METADATA: Store.setPadMetadata,
|
||||
CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin,
|
||||
GET_LAST_HASH: Store.getLastHash,
|
||||
GET_SNAPSHOT: Store.getSnapshot,
|
||||
// Drive
|
||||
DRIVE_USEROBJECT: Store.userObjectCommand,
|
||||
// Settings,
|
||||
|
|
|
@ -13,6 +13,7 @@ define([
|
|||
'/common/common-ui-elements.js',
|
||||
'/common/common-thumbnail.js',
|
||||
'/common/common-feedback.js',
|
||||
'/common/inner/snapshots.js',
|
||||
'/customize/application_config.js',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
'/common/test.js',
|
||||
|
@ -35,6 +36,7 @@ define([
|
|||
UIElements,
|
||||
Thumb,
|
||||
Feedback,
|
||||
Snapshots,
|
||||
AppConfig,
|
||||
ChainPad,
|
||||
Test)
|
||||
|
@ -43,6 +45,9 @@ define([
|
|||
|
||||
var UNINITIALIZED = 'UNINITIALIZED';
|
||||
|
||||
// History and snapshots mode shouldn't receive realtime data or push to chainpad
|
||||
var unsyncMode = false;
|
||||
|
||||
var STATE = Object.freeze({
|
||||
DISCONNECTED: 'DISCONNECTED',
|
||||
FORGOTTEN: 'FORGOTTEN',
|
||||
|
@ -50,7 +55,6 @@ define([
|
|||
INFINITE_SPINNER: 'INFINITE_SPINNER',
|
||||
ERROR: 'ERROR',
|
||||
INITIALIZING: 'INITIALIZING',
|
||||
HISTORY_MODE: 'HISTORY_MODE',
|
||||
READY: 'READY'
|
||||
});
|
||||
|
||||
|
@ -93,6 +97,7 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
var onLocal;
|
||||
var textContentGetter;
|
||||
var titleRecommender = function () { return false; };
|
||||
var contentGetter = function () { return UNINITIALIZED; };
|
||||
|
@ -125,20 +130,51 @@ define([
|
|||
return;
|
||||
};
|
||||
|
||||
var deleteSnapshot = function (hash) {
|
||||
var md = Util.clone(cpNfInner.metadataMgr.getMetadata());
|
||||
var snapshots = md.snapshots = md.snapshots || {};
|
||||
delete snapshots[hash];
|
||||
cpNfInner.metadataMgr.updateMetadata(md);
|
||||
onLocal();
|
||||
};
|
||||
var makeSnapshot = function (title, cb) {
|
||||
if (state !== STATE.READY) {
|
||||
return void cb('NOT_READY');
|
||||
}
|
||||
var sframeChan = common.getSframeChannel();
|
||||
sframeChan.query("Q_GET_LAST_HASH", null, function (err, obj) {
|
||||
if (err || (obj && obj.error)) { return void UI.warn(Messages.error); }
|
||||
var hash = obj.hash;
|
||||
if (!hash) { cb('NO_HASH'); return void UI.warn(Messages.error); }
|
||||
var md = Util.clone(cpNfInner.metadataMgr.getMetadata());
|
||||
var snapshots = md.snapshots = md.snapshots || {};
|
||||
if (snapshots[hash]) { cb('EEXISTS'); return void UI.warn(Messages.error); } // XXX
|
||||
snapshots[hash] = {
|
||||
title: title,
|
||||
time: +new Date()
|
||||
};
|
||||
cpNfInner.metadataMgr.updateMetadata(md);
|
||||
onLocal();
|
||||
cpNfInner.chainpad.onSettle(cb);
|
||||
});
|
||||
};
|
||||
|
||||
var stateChange = function (newState, text) {
|
||||
var wasEditable = (state === STATE.READY);
|
||||
if (state === STATE.DELETED || state === STATE.ERROR) { return; }
|
||||
if (state === STATE.INFINITE_SPINNER && newState !== STATE.READY) { return; }
|
||||
if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) {
|
||||
state = newState;
|
||||
} else if (newState === STATE.ERROR) {
|
||||
state = newState;
|
||||
} else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) {
|
||||
throw new Error("Cannot transition from DISCONNECTED to " + newState); // FIXME we are getting "DISCONNECTED to READY" on prod
|
||||
} else if (state !== STATE.READY && newState === STATE.HISTORY_MODE) {
|
||||
throw new Error("Cannot transition from " + state + " to " + newState);
|
||||
} else {
|
||||
state = newState;
|
||||
var wasEditable = (state === STATE.READY && !unsyncMode);
|
||||
if (newState !== state) {
|
||||
if (state === STATE.DELETED || state === STATE.ERROR) { return; }
|
||||
if (state === STATE.INFINITE_SPINNER && newState !== STATE.READY) { return; }
|
||||
if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) {
|
||||
state = newState;
|
||||
} else if (newState === STATE.ERROR) {
|
||||
state = newState;
|
||||
} else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) {
|
||||
throw new Error("Cannot transition from DISCONNECTED to " + newState); // FIXME we are getting "DISCONNECTED to READY" on prod
|
||||
} else {
|
||||
state = newState;
|
||||
}
|
||||
} else if (state === STATE.READY) {
|
||||
// Refreshing ready state
|
||||
}
|
||||
switch (state) {
|
||||
case STATE.DISCONNECTED:
|
||||
|
@ -187,8 +223,9 @@ define([
|
|||
}
|
||||
default:
|
||||
}
|
||||
if (wasEditable !== (state === STATE.READY)) {
|
||||
evEditableStateChange.fire(state === STATE.READY);
|
||||
var isEditable = (state === STATE.READY && !unsyncMode);
|
||||
if (wasEditable !== isEditable) {
|
||||
evEditableStateChange.fire(isEditable);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -205,8 +242,8 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var onLocal;
|
||||
var onRemote = function () {
|
||||
if (unsyncMode) { return; }
|
||||
if (state !== STATE.READY) { return; }
|
||||
|
||||
var oldContent = normalize(contentGetter());
|
||||
|
@ -261,9 +298,85 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var setUnsyncMode = function (bool) {
|
||||
if (unsyncMode === bool) { return; }
|
||||
unsyncMode = bool;
|
||||
evEditableStateChange.fire(state === STATE.READY && !unsyncMode);
|
||||
stateChange(state);
|
||||
};
|
||||
|
||||
// History mode:
|
||||
// When "bool" is true, we're entering in history mode
|
||||
// When "bool" is false and "update" is true, it means we're closing the history
|
||||
// and should update the content
|
||||
// When "bool" is false and "update" is false, it means we're restoring an old version,
|
||||
// no need to refresh
|
||||
var setHistoryMode = function (bool, update) {
|
||||
stateChange((bool) ? STATE.HISTORY_MODE : STATE.READY);
|
||||
if (!bool && !update && state !== STATE.READY) { return false; }
|
||||
cpNfInner.metadataMgr.setHistory(bool);
|
||||
toolbar.setHistory(bool);
|
||||
setUnsyncMode(bool);
|
||||
if (!bool && update) { onRemote(); }
|
||||
else {
|
||||
setTimeout(cpNfInner.metadataMgr.refresh);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
var closeSnapshot = function (restore) {
|
||||
if (restore && state !== STATE.READY) { return false; }
|
||||
toolbar.setSnapshot(false);
|
||||
setUnsyncMode(false); // Unlock onLocal and onRemote
|
||||
if (restore) { onLocal(); } // Restore? commit the content
|
||||
onRemote(); // Make sure we're back to the realtime content
|
||||
return true;
|
||||
};
|
||||
var loadSnapshot = function (hash, data) {
|
||||
setUnsyncMode(true);
|
||||
toolbar.setSnapshot(true);
|
||||
Snapshots.create(common, {
|
||||
readOnly: readOnly,
|
||||
$toolbar: $(toolbarContainer),
|
||||
hash: hash,
|
||||
data: data,
|
||||
close: closeSnapshot,
|
||||
applyVal: function (val) {
|
||||
var newContent = JSON.parse(val);
|
||||
var meta = extractMetadata(newContent);
|
||||
cpNfInner.metadataMgr.updateMetadata(meta);
|
||||
contentUpdate(normalize(newContent) || ["BODY",{},[]], function (h) {
|
||||
return h;
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Get the realtime metadata when in history mode
|
||||
var getLastMetadata = function () {
|
||||
if (!unsyncMode) { return; }
|
||||
var newContentStr = cpNfInner.chainpad.getUserDoc();
|
||||
var newContent = JSON.parse(newContentStr);
|
||||
var meta = extractMetadata(newContent);
|
||||
return meta;
|
||||
};
|
||||
var setLastMetadata = function (md) {
|
||||
if (!unsyncMode) { return; }
|
||||
if (state !== STATE.READY) { return; }
|
||||
var newContentStr = cpNfInner.chainpad.getAuthDoc();
|
||||
var newContent = JSON.parse(newContentStr);
|
||||
if (Array.isArray(newContent)) {
|
||||
newContent[3] = {
|
||||
metadata: md
|
||||
};
|
||||
} else {
|
||||
newContent.metadata = md;
|
||||
}
|
||||
try {
|
||||
cpNfInner.chainpad.contentUpdate(JSONSortify(newContent));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -281,6 +394,7 @@ define([
|
|||
*/
|
||||
|
||||
onLocal = function (/*padChange*/) {
|
||||
if (unsyncMode) { return; }
|
||||
if (state !== STATE.READY) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
|
@ -324,6 +438,35 @@ define([
|
|||
window.dispatchEvent(evt);
|
||||
};
|
||||
|
||||
var versionHashEl;
|
||||
var onInit = function () {
|
||||
UI.updateLoadingProgress({
|
||||
state: 2,
|
||||
progress: 0.1
|
||||
}, false);
|
||||
stateChange(STATE.INITIALIZING);
|
||||
if ($('.cp-help-container').length) {
|
||||
var privateDat = cpNfInner.metadataMgr.getPrivateData();
|
||||
// Burn after reading warning
|
||||
$('.cp-help-container').before(common.getBurnAfterReadingWarning());
|
||||
// Versioned link warning
|
||||
if (privateDat.isHistoryVersion) {
|
||||
versionHashEl = h('div.alert.alert-warning.cp-burn-after-reading');
|
||||
$('.cp-help-container').before(versionHashEl);
|
||||
}
|
||||
}
|
||||
|
||||
common.getSframeChannel().on('EV_VERSION_TIME', function (time) {
|
||||
if (!versionHashEl) { return; }
|
||||
var vTime = time;
|
||||
var vTimeStr = vTime ? new Date(vTime).toLocaleString()
|
||||
: 'v' + privateDat.isHistoryVersion;
|
||||
var vTxt = Messages._getKey('infobar_versionHash', [vTimeStr]);
|
||||
versionHashEl.innerText = vTxt;
|
||||
versionHashEl = undefined;
|
||||
});
|
||||
};
|
||||
|
||||
var onReady = function () {
|
||||
var newContentStr = cpNfInner.chainpad.getUserDoc();
|
||||
if (state === STATE.DELETED) { return; }
|
||||
|
@ -341,6 +484,7 @@ define([
|
|||
var privateDat = cpNfInner.metadataMgr.getPrivateData();
|
||||
var type = privateDat.app;
|
||||
|
||||
|
||||
// contentUpdate may be async so we need an nthen here
|
||||
nThen(function (waitFor) {
|
||||
if (!newPad) {
|
||||
|
@ -354,7 +498,9 @@ define([
|
|||
}
|
||||
cpNfInner.metadataMgr.updateMetadata(metadata);
|
||||
newContent = normalize(newContent);
|
||||
contentUpdate(newContent, waitFor);
|
||||
if (!unsyncMode) {
|
||||
contentUpdate(newContent, waitFor);
|
||||
}
|
||||
} else {
|
||||
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
|
||||
// We're getting 'new pad' but there is an existing file
|
||||
|
@ -590,13 +736,7 @@ define([
|
|||
},
|
||||
onRemote: onRemote,
|
||||
onLocal: onLocal,
|
||||
onInit: function () {
|
||||
UI.updateLoadingProgress({
|
||||
state: 2,
|
||||
progress: 0.1
|
||||
}, false);
|
||||
stateChange(STATE.INITIALIZING);
|
||||
},
|
||||
onInit: onInit,
|
||||
onReady: function () { evStart.reg(onReady); },
|
||||
onConnectionChange: onConnectionChange,
|
||||
onError: onError,
|
||||
|
@ -691,17 +831,29 @@ define([
|
|||
onLocal: onLocal,
|
||||
onRemote: onRemote,
|
||||
setHistory: setHistoryMode,
|
||||
extractMetadata: extractMetadata, // extract from current version
|
||||
getLastMetadata: getLastMetadata, // get from authdoc
|
||||
setLastMetadata: setLastMetadata, // set to userdoc/authdoc
|
||||
applyVal: function (val) {
|
||||
contentUpdate(JSON.parse(val) || ["BODY",{},[]], function (h) {
|
||||
var newContent = JSON.parse(val);
|
||||
var meta = extractMetadata(newContent);
|
||||
cpNfInner.metadataMgr.updateMetadata(meta);
|
||||
contentUpdate(normalize(newContent) || ["BODY",{},[]], function (h) {
|
||||
return h;
|
||||
});
|
||||
},
|
||||
$toolbar: $(toolbarContainer)
|
||||
};
|
||||
var $hist = common.createButton('history', true, {histConfig: histConfig});
|
||||
$hist.addClass('cp-hidden-if-readonly');
|
||||
toolbar.$drawer.append($hist);
|
||||
|
||||
var $snapshot = common.createButton('snapshots', true, {
|
||||
remove: deleteSnapshot,
|
||||
make: makeSnapshot,
|
||||
load: loadSnapshot
|
||||
});
|
||||
toolbar.$drawer.append($snapshot);
|
||||
|
||||
var $copy = common.createButton('copy', true);
|
||||
toolbar.$drawer.append($copy);
|
||||
|
||||
|
@ -767,7 +919,7 @@ define([
|
|||
onEditableChange: evEditableStateChange.reg,
|
||||
|
||||
// Determine whether the UI should be locked for editing.
|
||||
isLocked: function () { return state !== STATE.READY; },
|
||||
isLocked: function () { return state !== STATE.READY || unsyncMode; },
|
||||
|
||||
// Determine whether the pad is a "read only" pad and cannot be changed.
|
||||
isReadOnly: function () { return readOnly; },
|
||||
|
|
|
@ -28,12 +28,19 @@ define([], function () {
|
|||
var padRpc = conf.padRpc;
|
||||
var sframeChan = conf.sframeChan;
|
||||
var metadata= conf.metadata || {};
|
||||
var versionHash = conf.versionHash;
|
||||
var validateKey = metadata.validateKey;
|
||||
var onConnect = conf.onConnect || function () { };
|
||||
var lastTime; // Time of last patch (if versioned link);
|
||||
conf = undefined;
|
||||
|
||||
if (versionHash) { readOnly = true; }
|
||||
|
||||
padRpc.onReadyEvent.reg(function () {
|
||||
sframeChan.event('EV_RT_READY', null);
|
||||
if (lastTime && versionHash) {
|
||||
sframeChan.event('EV_VERSION_TIME', lastTime);
|
||||
}
|
||||
});
|
||||
|
||||
// shim between chainpad and netflux
|
||||
|
@ -83,6 +90,7 @@ define([], function () {
|
|||
}
|
||||
var message = msgIn(msgObj.user, msgObj.msg);
|
||||
if (!message) { return; }
|
||||
lastTime = msgObj.time;
|
||||
|
||||
verbose(message);
|
||||
|
||||
|
@ -132,6 +140,7 @@ define([], function () {
|
|||
padRpc.joinPad({
|
||||
channel: channel || null,
|
||||
readOnly: readOnly,
|
||||
versionHash: versionHash,
|
||||
metadata: metadata
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-util.js',
|
||||
'/common/hyperscript.js',
|
||||
'/customize/messages.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
//'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
], function ($, UI, nThen, ChainPad /* JsonOT */) {
|
||||
], function ($, UI, Util, h, Messages, nThen, ChainPad /* JsonOT */) {
|
||||
//var ChainPad = window.ChainPad;
|
||||
var History = {};
|
||||
|
||||
History.create = function (common, config) {
|
||||
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
|
||||
if (History.loading) { return void console.error("History is already being loaded..."); }
|
||||
if (History.state) { return void console.error("Already loaded"); }
|
||||
History.loading = true;
|
||||
History.state = true;
|
||||
var $toolbar = config.$toolbar;
|
||||
var $hist = $toolbar.find('.cp-toolbar-history');
|
||||
$hist.addClass('cp-history-init');
|
||||
|
||||
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
|
||||
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
|
||||
|
@ -54,6 +61,140 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
var realtime;
|
||||
var states = [];
|
||||
var patchWidth = 0;
|
||||
var c = 0;//states.length - 1;
|
||||
|
||||
var getIndex = function (i) {
|
||||
return states.length - 1 + i;
|
||||
};
|
||||
var getRank = function (idx) {
|
||||
return idx - states.length + 1;
|
||||
};
|
||||
// Get the author or group of author linked to a state
|
||||
var getAuthor = function (idx, semantic) {
|
||||
if (semantic === 1 || !config.extractMetadata) {
|
||||
return states[idx].author;
|
||||
}
|
||||
try {
|
||||
var val = JSON.parse(states[idx].getContent().doc);
|
||||
var md = config.extractMetadata(val);
|
||||
var users = Object.keys(md.users).sort();
|
||||
return users.join();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return states[idx].author;
|
||||
}
|
||||
};
|
||||
|
||||
var bar = h('span.cp-history-timeline-bar');
|
||||
var onResize = function () {
|
||||
var $bar = $(bar);
|
||||
if (!$bar.width() || !$bar.length) { return; }
|
||||
var widthPx = patchWidth * $bar.width() / 100;
|
||||
$hist.removeClass('cp-smallpatch');
|
||||
$bar.find('.cp-history-snapshot').css('margin-left', "");
|
||||
var $pos = $hist.find('.cp-history-timeline-pos');
|
||||
$pos.css('margin-left', "");
|
||||
if (widthPx < 18) {
|
||||
$hist.addClass('cp-smallpatch');
|
||||
$bar.find('.cp-history-snapshot').css('margin-left', (widthPx/2-2)+"px");
|
||||
$pos.css('margin-left', (widthPx/2-2)+"px");
|
||||
}
|
||||
};
|
||||
|
||||
// Refresh the timeline UI with the block states
|
||||
var refreshBar = function (snapshotsOnly) {
|
||||
var $pos = $hist.find('.cp-history-timeline-pos');
|
||||
var $bar = $(bar);
|
||||
var users = {
|
||||
list: [],
|
||||
author: '',
|
||||
el: undefined,
|
||||
i: 0
|
||||
};
|
||||
var user = {
|
||||
list: [],
|
||||
author: '',
|
||||
el: undefined,
|
||||
i: 0
|
||||
};
|
||||
|
||||
var snapshotsData = {};
|
||||
var snapshots = [];
|
||||
if (config.getLastMetadata) {
|
||||
try {
|
||||
var md = config.getLastMetadata();
|
||||
if (md.snapshots) {
|
||||
snapshotsData = md.snapshots;
|
||||
snapshots = Object.keys(md.snapshots);
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
var max = states.length - 1;
|
||||
var snapshotsEl = [];
|
||||
patchWidth = 100 / max;
|
||||
|
||||
// Check if we need a new block on the index i for the "obj" type (user or users)
|
||||
var check = function (obj, author, i) {
|
||||
if (snapshotsOnly) { return; }
|
||||
if (obj.author !== author) {
|
||||
obj.author = author;
|
||||
if (obj.el) {
|
||||
$(obj.el).css('width', (100*(i - obj.i)/max)+'%');
|
||||
}
|
||||
obj.el = h('span.cp-history-bar-el');
|
||||
obj.list.push(obj.el);
|
||||
obj.i = i;
|
||||
}
|
||||
};
|
||||
|
||||
var hash;
|
||||
for (var i = 1; i < states.length; i++) {
|
||||
hash = states[i].serverHash;
|
||||
if (snapshots.indexOf(hash) !== -1) {
|
||||
snapshotsEl.push(h('div.cp-history-snapshot', {
|
||||
style: 'width:'+patchWidth+'%;left:'+(patchWidth * (i-1))+'%;',
|
||||
title: snapshotsData[hash].title
|
||||
}, h('i.fa.fa-camera')));
|
||||
}
|
||||
if (config.drive) {
|
||||
// Display only one bar, split by patch
|
||||
check(user, i, i);
|
||||
} else {
|
||||
// Display two bars, split by author(s)
|
||||
check(user, getAuthor(i, 1), i);
|
||||
check(users, getAuthor(i, 2), i);
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshotsOnly) {
|
||||
// We only want to redraw the snapshots
|
||||
$bar.find('.cp-history-snapshots').html('').append([
|
||||
$pos,
|
||||
snapshotsEl
|
||||
]);
|
||||
} else {
|
||||
$(user.el).css('width', (100*(max + 1 - user.i)/max)+'%');
|
||||
if (!config.drive) {
|
||||
$(users.el).css('width', (100*(max + 1 - users.i)/max)+'%');
|
||||
}
|
||||
|
||||
$bar.html('').append([
|
||||
h('span.cp-history-timeline-users', users.list),
|
||||
h('span.cp-history-timeline-user', user.list),
|
||||
h('div.cp-history-snapshots', [
|
||||
$pos[0],
|
||||
snapshotsEl
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
onResize();
|
||||
};
|
||||
|
||||
var allMessages = [];
|
||||
var lastKnownHash;
|
||||
var isComplete = false;
|
||||
|
@ -71,11 +212,18 @@ define([
|
|||
lastKnownHash = data.lastKnownHash;
|
||||
isComplete = data.isFull;
|
||||
var messages = (data.messages || []).map(function (obj) {
|
||||
if (!config.debug) {
|
||||
return obj.msg;
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
// We're supposed to receive 2 checkpoints. If the result is only ONE message
|
||||
// and this message is a checkpoint, it means it's the last message of the history
|
||||
// (and this is a trimmed history)
|
||||
if (messages.length === 1) {
|
||||
var parsed = JSON.parse(messages[0].msg);
|
||||
if (parsed[0] === 4) {
|
||||
isComplete = true;
|
||||
}
|
||||
}
|
||||
if (config.debug) { console.log(data.messages); }
|
||||
Array.prototype.unshift.apply(allMessages, messages); // Destructive concat
|
||||
fillChainPad(realtime, allMessages);
|
||||
|
@ -95,23 +243,38 @@ define([
|
|||
console.error(e);
|
||||
}
|
||||
};
|
||||
var onClose = function () { config.setHistory(false, true); };
|
||||
var onClose = function () {
|
||||
config.setHistory(false, true);
|
||||
};
|
||||
|
||||
var onRevert = function () {
|
||||
config.setHistory(false, false);
|
||||
// Before we can restore the current version, we need to update metadataMgr
|
||||
// so that it will uses the snapshots from the realtime version!
|
||||
// Restoring the snapshots to their old version would go against the
|
||||
// goal of having snapshots
|
||||
if (config.getLastMetadata) {
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
var lastMd = config.getLastMetadata();
|
||||
var _snapshots = lastMd.snapshots;
|
||||
var _users = lastMd.users;
|
||||
var md = Util.clone(metadataMgr.getMetadata());
|
||||
md.snapshots = _snapshots;
|
||||
md.users = _users;
|
||||
metadataMgr.updateMetadata(md);
|
||||
}
|
||||
|
||||
// And now we can properly restore the content
|
||||
var closed = config.setHistory(false, false);
|
||||
if (!closed) {
|
||||
return void UI.alert(Messages.history_cantRestore);
|
||||
}
|
||||
config.onLocal();
|
||||
config.onRemote();
|
||||
return true;
|
||||
};
|
||||
|
||||
config.setHistory(true);
|
||||
|
||||
var Messages = common.Messages;
|
||||
|
||||
var realtime;
|
||||
|
||||
var states = [];
|
||||
var c = 0;//states.length - 1;
|
||||
|
||||
var $hist = $toolbar.find('.cp-toolbar-history');
|
||||
var $bottom = $toolbar.find('.cp-toolbar-bottom');
|
||||
var $cke = $toolbar.find('.cke_toolbox_main');
|
||||
|
||||
|
@ -121,46 +284,46 @@ define([
|
|||
|
||||
UI.spinner($hist).get().show();
|
||||
|
||||
var onUpdate;
|
||||
|
||||
var update = function (newRt) {
|
||||
realtime = newRt;
|
||||
if (!realtime) { return []; }
|
||||
states = getStates(realtime);
|
||||
if (typeof onUpdate === "function") { onUpdate(); }
|
||||
refreshBar();
|
||||
return states;
|
||||
};
|
||||
|
||||
var $loadMore, $version, get;
|
||||
var $loadMore, $time, get;
|
||||
|
||||
// Get the content of the selected version, and change the version number
|
||||
var loading = false;
|
||||
var loadMore = function (cb) {
|
||||
if (loading) { return; }
|
||||
loading = true;
|
||||
$loadMore.removeClass('fa fa-ellipsis-h')
|
||||
.append($('<span>', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'}));
|
||||
$loadMore.find('.fa-ellipsis-h').hide();
|
||||
$loadMore.find('.fa-refresh').show();
|
||||
|
||||
loadMoreHistory(config, common, function (err, newRt, isFull) {
|
||||
if (err === 'EFULL') {
|
||||
$loadMore.off('click').hide();
|
||||
get(c);
|
||||
$version.show();
|
||||
return;
|
||||
}
|
||||
loading = false;
|
||||
if (err) { return void console.error(err); }
|
||||
update(newRt);
|
||||
$loadMore.addClass('fa fa-ellipsis-h').html('');
|
||||
$loadMore.find('.fa-ellipsis-h').show();
|
||||
$loadMore.find('.fa-refresh').hide();
|
||||
get(c);
|
||||
if (isFull) {
|
||||
$loadMore.off('click').hide();
|
||||
$version.show();
|
||||
}
|
||||
if (cb) { cb(); }
|
||||
});
|
||||
};
|
||||
get = function (i) {
|
||||
|
||||
// semantic === 1 : group by user
|
||||
// semantic === 2 : group by "group of users"
|
||||
get = function (i, blockOnly, semantic) {
|
||||
i = parseInt(i);
|
||||
if (isNaN(i)) { return; }
|
||||
if (i > 0) { i = 0; }
|
||||
|
@ -168,29 +331,45 @@ define([
|
|||
if (i <= -(states.length - 11)) {
|
||||
loadMore();
|
||||
}
|
||||
var idx = states.length - 1 + i;
|
||||
|
||||
var idx = getIndex(i);
|
||||
if (semantic && i !== c) {
|
||||
// If semantic is true, jump to the next patch from a different netflux ID
|
||||
var author = getAuthor(idx, semantic);
|
||||
var forward = i > c;
|
||||
for (var j = idx; (j > 0 && j < states.length ); (forward ? j++ : j--)) {
|
||||
if (author !== getAuthor(j, semantic)) {
|
||||
break;
|
||||
}
|
||||
idx = j;
|
||||
i = getRank(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (blockOnly) { return states[idx]; }
|
||||
|
||||
var val = states[idx].getContent().doc;
|
||||
c = i;
|
||||
if (typeof onUpdate === "function") { onUpdate(); }
|
||||
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' +
|
||||
'.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous')
|
||||
.css('visibility', '');
|
||||
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous')
|
||||
.prop('disabled', '');
|
||||
if (c === -(states.length-1)) {
|
||||
$hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden');
|
||||
$hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden');
|
||||
$hist.find('.cp-toolbar-history-previous').prop('disabled', 'disabled');
|
||||
}
|
||||
if (c === 0) {
|
||||
$hist.find('.cp-toolbar-history-next').css('visibility', 'hidden');
|
||||
$hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden');
|
||||
$hist.find('.cp-toolbar-history-next').prop('disabled', 'disabled');
|
||||
}
|
||||
var $pos = $hist.find('.cp-toolbar-history-pos');
|
||||
var p = 100 * (1 - (-c / (states.length-2)));
|
||||
$pos.css('margin-left', p+'%');
|
||||
var $pos = $hist.find('.cp-history-timeline-pos');
|
||||
var p = 100 * (1 - (-(c - 1) / (states.length-1)));
|
||||
$pos.css('left', p+'%');
|
||||
$pos.css('width', patchWidth+'%');
|
||||
|
||||
// Display the version when the full history is loaded
|
||||
// Note: the first version is always empty and probably can't be displayed, so
|
||||
// we can consider we have only states.length - 1 versions
|
||||
$version.text(idx + ' / ' + (states.length-1));
|
||||
var time = states[idx].time;
|
||||
if (time) {
|
||||
$time.text(new Date(time).toLocaleString());
|
||||
} else { $time.text(''); }
|
||||
|
||||
if (config.debug) {
|
||||
console.log(states[idx]);
|
||||
|
@ -203,77 +382,186 @@ define([
|
|||
return val || '';
|
||||
};
|
||||
|
||||
/*
|
||||
var getNext = function (step) {
|
||||
return typeof step === "number" ? get(c + step) : get(c + 1);
|
||||
};
|
||||
var getPrevious = function (step) {
|
||||
return typeof step === "number" ? get(c - step) : get(c - 1);
|
||||
};
|
||||
*/
|
||||
|
||||
var makeSnapshot = function (title) {
|
||||
var idx = getIndex(c);
|
||||
if (!config.getLastMetadata || !config.setLastMetadata) { return; }
|
||||
try {
|
||||
var block = states[idx];
|
||||
var hash = block.serverHash;
|
||||
var md = config.getLastMetadata();
|
||||
md.snapshots = md.snapshots || {};
|
||||
if (md.snapshots[hash]) { return; }
|
||||
md.snapshots[hash] = {
|
||||
title: title,
|
||||
time: block.time ? (+new Date(block.time)) : +new Date()
|
||||
};
|
||||
var sent = config.setLastMetadata(md);
|
||||
if (!sent) { return void UI.alert(Messages.snapshots_cantMake); }
|
||||
refreshBar();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Create the history toolbar
|
||||
var display = function () {
|
||||
$hist.html('');
|
||||
$hist.removeClass('cp-history-init');
|
||||
|
||||
var $rev = $('<button>', {
|
||||
'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o',
|
||||
title: Messages.history_restoreTitle
|
||||
}).appendTo($hist);//.text(Messages.history_restore);
|
||||
if (History.readOnly) { $rev.css('visibility', 'hidden'); }
|
||||
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
|
||||
var $fastPrev = $('<button>', {
|
||||
'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary',
|
||||
title: Messages.history_prev
|
||||
}).appendTo($hist);
|
||||
var $prev =$('<button>', {
|
||||
'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary',
|
||||
title: Messages.history_prev
|
||||
}).appendTo($hist);
|
||||
var $nav = $('<div>', {'class': 'cp-toolbar-history-goto'}).appendTo($hist);
|
||||
var $next = $('<button>', {
|
||||
'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary',
|
||||
title: Messages.history_next
|
||||
}).appendTo($hist);
|
||||
var $fastNext = $('<button>', {
|
||||
'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary',
|
||||
title: Messages.history_next
|
||||
}).appendTo($hist);
|
||||
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
|
||||
var $close = $('<button>', {
|
||||
'class':'cp-toolbar-history-close fa fa-window-close',
|
||||
title: Messages.history_closeTitle
|
||||
}).appendTo($hist);
|
||||
var fastPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_fastPrev }, [
|
||||
h('i.fa.fa-step-backward'),
|
||||
h('i.fa.fa-users')
|
||||
]);
|
||||
var userPrev = h('button.cp-toolbar-history-previous', { title: Messages.history_userPrev }, [
|
||||
h('i.fa.fa-step-backward'),
|
||||
h('i.fa.fa-user')
|
||||
]);
|
||||
var prev = h('button.cp-toolbar-history-previous', { title: Messages.history_prev }, [
|
||||
h('i.fa.fa-step-backward')
|
||||
]);
|
||||
var fastNext = h('button.cp-toolbar-history-next', { title: Messages.history_fastNext }, [
|
||||
h('i.fa.fa-users'),
|
||||
h('i.fa.fa-step-forward'),
|
||||
]);
|
||||
var userNext = h('button.cp-toolbar-history-next', { title: Messages.history_userNext }, [
|
||||
h('i.fa.fa-user'),
|
||||
h('i.fa.fa-step-forward'),
|
||||
]);
|
||||
var next = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
|
||||
h('i.fa.fa-step-forward')
|
||||
]);
|
||||
if (config.drive) {
|
||||
fastNext = h('button.cp-toolbar-history-next', { title: Messages.history_next }, [
|
||||
h('i.fa.fa-fast-forward'),
|
||||
]);
|
||||
fastPrev = h('button.cp-toolbar-history-previous', {title: Messages.history_prev}, [
|
||||
h('i.fa.fa-fast-backward'),
|
||||
]);
|
||||
}
|
||||
|
||||
var $bar = $('<div>', {'class': 'cp-toolbar-history-bar'}).appendTo($nav);
|
||||
var $container = $('<div>', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar);
|
||||
$('<div>', {'class': 'cp-toolbar-history-pos'}).appendTo($container);
|
||||
var $fastPrev = $(fastPrev);
|
||||
var $userPrev = $(userPrev);
|
||||
var $prev = $(prev);
|
||||
var $fastNext = $(fastNext);
|
||||
var $userNext = $(userNext);
|
||||
var $next = $(next);
|
||||
|
||||
$version = $('<span>', {
|
||||
'class': 'cp-toolbar-history-version'
|
||||
}).prependTo($bar).hide();
|
||||
$loadMore = $('<button>', {
|
||||
'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h',
|
||||
title: Messages.history_loadMore
|
||||
}).click(function () {
|
||||
var _loadMore = h('button.cp-toolbar-history-loadmore', { title: Messages.history_loadMore }, [
|
||||
h('i.fa.fa-ellipsis-h'),
|
||||
h('i.fa.fa-refresh.fa-spin.fa-3x.fa-fw', { style: 'display: none;' })
|
||||
]);
|
||||
|
||||
var pos = h('span.cp-history-timeline-pos.fa.fa-caret-down');
|
||||
var time = h('div.cp-history-timeline-time');
|
||||
$time = $(time);
|
||||
var timeline = h('div.cp-toolbar-history-timeline', [
|
||||
h('div.cp-history-timeline-line', [
|
||||
h('span.cp-history-timeline-legend', [
|
||||
h('i.fa.fa-users'),
|
||||
h('i.fa.fa-user')
|
||||
]),
|
||||
h('span.cp-history-timeline-loadmore', _loadMore),
|
||||
h('span.cp-history-timeline-container', [
|
||||
bar
|
||||
])
|
||||
]),
|
||||
h('div.cp-history-timeline-actions', [
|
||||
h('span.cp-history-timeline-prev', [
|
||||
fastPrev,
|
||||
config.drive ? undefined : userPrev,
|
||||
prev,
|
||||
]),
|
||||
time,
|
||||
h('span.cp-history-timeline-next', [
|
||||
next,
|
||||
config.drive ? undefined : userNext,
|
||||
fastNext
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
var snapshot = h('button', {
|
||||
title: Messages.snapshots_new,
|
||||
}, [
|
||||
h('i.fa.fa-camera')
|
||||
]);
|
||||
var share = h('button', { title: Messages.history_shareTitle }, [
|
||||
h('i.fa.fa-shhare-alt'),
|
||||
h('span', Messages.shareButton)
|
||||
]);
|
||||
var restoreTitle = config.drive ? Messages.history_restoreDriveTitle
|
||||
: Messages.history_restoreTitle;
|
||||
var restore = h('button', {
|
||||
title: restoreTitle,
|
||||
}, [
|
||||
h('i.fa.fa-check'),
|
||||
h('span', Messages.history_restore)
|
||||
]);
|
||||
var close = h('button', { title: Messages.history_closeTitle }, [
|
||||
h('i.fa.fa-times'),
|
||||
h('span', Messages.history_close)
|
||||
]);
|
||||
var actions = h('div.cp-toolbar-history-actions', [
|
||||
h('span.cp-history-actions-first', [
|
||||
snapshot,
|
||||
share
|
||||
]),
|
||||
h('span.cp-history-actions-last', [
|
||||
restore,
|
||||
close
|
||||
])
|
||||
]);
|
||||
|
||||
if (History.readOnly) {
|
||||
snapshot.disabled = true;
|
||||
restore.disabled = true;
|
||||
}
|
||||
if (config.drive) {
|
||||
$hist.addClass('cp-history-drive');
|
||||
$(snapshot).hide();
|
||||
$(share).hide();
|
||||
}
|
||||
|
||||
$hist.append([timeline, actions]);
|
||||
onResize();
|
||||
$(window).on('resize', onResize);
|
||||
|
||||
var $bar = $(bar);
|
||||
$bar.find('.cp-history-snapshots').append(pos);
|
||||
$bar.click(function (e) {
|
||||
e.stopPropagation();
|
||||
var $t = $(e.target);
|
||||
if ($t.closest('.cp-history-snapshot').length) {
|
||||
$t = $t.closest('.cp-history-snapshot');
|
||||
}
|
||||
var isEl = $t.is('.cp-history-snapshot');
|
||||
if (!$t.is('.cp-history-snapshots') && !isEl) { return; }
|
||||
var x = e.offsetX;
|
||||
if (isEl) {
|
||||
x += $t.position().left;
|
||||
}
|
||||
var p = x / $bar.width();
|
||||
var v = 1-Math.ceil((states.length - 1) * (1 - p));
|
||||
render(get(v));
|
||||
});
|
||||
$loadMore = $(_loadMore).click(function () {
|
||||
loadMore(function () {
|
||||
get(c);
|
||||
});
|
||||
}).prependTo($container);
|
||||
|
||||
// Load a version when clicking on the bar
|
||||
$container.click(function (e) {
|
||||
e.stopPropagation();
|
||||
if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; }
|
||||
var p = e.offsetX / $container.width();
|
||||
var v = -Math.round((states.length - 1) * (1 - p));
|
||||
render(get(v));
|
||||
});
|
||||
|
||||
onUpdate = function () {
|
||||
// Called when a new version is loaded
|
||||
};
|
||||
|
||||
var onKeyDown, onKeyUp;
|
||||
var close = function () {
|
||||
var closeUI = function () {
|
||||
History.state = false;
|
||||
$hist.hide();
|
||||
$bottom.show();
|
||||
$cke.show();
|
||||
|
@ -283,33 +571,93 @@ define([
|
|||
};
|
||||
|
||||
// Version buttons
|
||||
$prev.click(function () { render(getPrevious()); });
|
||||
$next.click(function () { render(getNext()); });
|
||||
$fastPrev.click(function () { render(getPrevious(10)); });
|
||||
$fastNext.click(function () { render(getNext(10)); });
|
||||
$prev.click(function () { render(get(c - 1)); });
|
||||
$next.click(function () { render(get(c + 1)); });
|
||||
if (config.drive) {
|
||||
$fastPrev.click(function () { render(get(c - 10)); });
|
||||
$fastNext.click(function () { render(get(c + 10)); });
|
||||
$userPrev.click(function () { render(get(c - 10)); });
|
||||
$userNext.click(function () { render(get(c + 10)); });
|
||||
} else {
|
||||
$userPrev.click(function () { render(get(c - 1, false, 1)); });
|
||||
$userNext.click(function () { render(get(c + 1, false, 1)); });
|
||||
$fastPrev.click(function () { render(get(c - 1, false, 2)); });
|
||||
$fastNext.click(function () { render(get(c + 1, false, 2)); });
|
||||
}
|
||||
onKeyDown = function (e) {
|
||||
var p = function () { e.preventDefault(); };
|
||||
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
|
||||
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
|
||||
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
|
||||
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
|
||||
if (e.which === 27) { p(); $close.click(); }
|
||||
if (e.which === 39) { p(); return $next.click(); } // Right
|
||||
if (e.which === 37) { p(); return $prev.click(); } // Left
|
||||
if (e.which === 38) { p(); return $userNext.click(); } // Up
|
||||
if (e.which === 40) { p(); return $userPrev.click(); } // Down
|
||||
if (e.which === 33) { p(); return $fastNext.click(); } // PageUp
|
||||
if (e.which === 34) { p(); return $fastPrev.click(); } // PageUp
|
||||
if (e.which === 27) { p(); $(close).click(); }
|
||||
};
|
||||
onKeyUp = function (e) { e.stopPropagation(); };
|
||||
$(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
|
||||
|
||||
// Close & restore buttons
|
||||
$close.click(function () {
|
||||
states = [];
|
||||
close();
|
||||
onClose();
|
||||
// Snapshots
|
||||
$(snapshot).click(function () {
|
||||
var input = h('input', {
|
||||
placeholder: Messages.snapshots_placeholder
|
||||
});
|
||||
var $input = $(input);
|
||||
var content = h('div', [
|
||||
h('h5', Messages.snapshots_new),
|
||||
input
|
||||
]);
|
||||
|
||||
var buttons = [{
|
||||
className: 'cancel',
|
||||
name: Messages.filePicker_close,
|
||||
onClick: function () {},
|
||||
keys: [27],
|
||||
}, {
|
||||
className: 'primary',
|
||||
iconClass: '.fa.fa-camera',
|
||||
name: Messages.snapshots_new,
|
||||
onClick: function () {
|
||||
var val = $input.val();
|
||||
if (!val) { return true; }
|
||||
makeSnapshot(val);
|
||||
},
|
||||
keys: [],
|
||||
}];
|
||||
|
||||
UI.openCustomModal(UI.dialog.customModal(content, {buttons: buttons }));
|
||||
setTimeout(function () {
|
||||
$input.focus();
|
||||
});
|
||||
});
|
||||
$rev.click(function () {
|
||||
UI.confirm(Messages.history_restorePrompt, function (yes) {
|
||||
|
||||
// Share
|
||||
$(share).click(function () {
|
||||
var block = get(c, true);
|
||||
common.getSframeChannel().event('EV_SHARE_OPEN', {
|
||||
versionHash: block.serverHash,
|
||||
//title: title
|
||||
});
|
||||
});
|
||||
|
||||
// Close & restore buttons
|
||||
$(close).click(function () {
|
||||
states = [];
|
||||
onClose();
|
||||
closeUI();
|
||||
});
|
||||
$(restore).click(function () {
|
||||
var restorePrompt = config.drive ? Messages.history_restoreDrivePrompt
|
||||
: Messages.history_restorePrompt;
|
||||
UI.confirm(restorePrompt, function (yes) {
|
||||
if (!yes) { return; }
|
||||
close();
|
||||
onRevert();
|
||||
UI.log(Messages.history_restoreDone);
|
||||
var done = onRevert();
|
||||
if (done) {
|
||||
closeUI();
|
||||
var restoreDone = config.drive ? Messages.history_restoreDriveDone
|
||||
: Messages.history_restoreDone;
|
||||
UI.log(restoreDone);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -328,11 +676,9 @@ define([
|
|||
History.loading = false;
|
||||
if (err) { throw new Error(err); }
|
||||
update(newRt);
|
||||
c = states.length - 1;
|
||||
display();
|
||||
if (isFull) {
|
||||
$loadMore.off('click').hide();
|
||||
$version.show();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -465,6 +465,7 @@ define([
|
|||
feedbackAllowed: Utils.Feedback.state,
|
||||
isPresent: parsed.hashData && parsed.hashData.present,
|
||||
isEmbed: parsed.hashData && parsed.hashData.embed,
|
||||
isHistoryVersion: parsed.hashData && parsed.hashData.versionHash,
|
||||
accounts: {
|
||||
donateURL: Cryptpad.donateURL,
|
||||
upgradeURL: Cryptpad.upgradeURL
|
||||
|
@ -1116,6 +1117,7 @@ define([
|
|||
// We don't need it since the message is already validated serverside by hk
|
||||
return {
|
||||
msg: crypto.decrypt(obj.msg, true, true),
|
||||
serverHash: obj.serverHash,
|
||||
author: obj.author,
|
||||
time: obj.time
|
||||
};
|
||||
|
@ -1451,6 +1453,26 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
sframeChan.on('Q_GET_LAST_HASH', function (data, cb) {
|
||||
Cryptpad.padRpc.getLastHash({
|
||||
channel: secret.channel
|
||||
}, cb);
|
||||
});
|
||||
sframeChan.on('Q_GET_SNAPSHOT', function (data, cb) {
|
||||
var crypto = Crypto.createEncryptor(secret.keys);
|
||||
Cryptpad.padRpc.getSnapshot({
|
||||
channel: secret.channel,
|
||||
hash: data.hash
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { return void cb(obj); }
|
||||
var messages = obj.messages || [];
|
||||
messages.forEach(function (patch) {
|
||||
patch.msg = crypto.decrypt(patch.msg, true, true);
|
||||
});
|
||||
cb(messages);
|
||||
});
|
||||
});
|
||||
|
||||
if (cfg.messaging) {
|
||||
Notifier.getPermission();
|
||||
|
||||
|
@ -1530,6 +1552,7 @@ define([
|
|||
var cpNfCfg = {
|
||||
sframeChan: sframeChan,
|
||||
channel: secret.channel,
|
||||
versionHash: parsed.hashData && parsed.hashData.versionHash,
|
||||
padRpc: Cryptpad.padRpc,
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
isNewHash: isNewHash,
|
||||
|
|
|
@ -33,6 +33,7 @@ MessengerUI, Messages) {
|
|||
var FILE_CLS = Bar.constants.file = 'cp-toolbar-file';
|
||||
var DRAWER_CLS = Bar.constants.drawer = 'cp-toolbar-drawer-content';
|
||||
var HISTORY_CLS = Bar.constants.history = 'cp-toolbar-history';
|
||||
var SNAPSHOTS_CLS = Bar.constants.history = 'cp-toolbar-snapshots';
|
||||
|
||||
// Userlist
|
||||
var USERLIST_CLS = Bar.constants.userlist = "cp-toolbar-users";
|
||||
|
@ -87,6 +88,7 @@ MessengerUI, Messages) {
|
|||
h('div.'+BOTTOM_RIGHT_CLS)
|
||||
])).appendTo($toolbar);
|
||||
$toolbar.append(h('div.'+HISTORY_CLS));
|
||||
$toolbar.append(h('div.'+SNAPSHOTS_CLS));
|
||||
|
||||
var $file = $toolbar.find('.'+BOTTOM_LEFT_CLS);
|
||||
|
||||
|
@ -659,6 +661,8 @@ MessengerUI, Messages) {
|
|||
.text('('+Messages.readonly+')'));
|
||||
return $titleContainer;
|
||||
}
|
||||
$hoverable.append($('<span>', {'class': 'cp-toolbar-title-readonly cp-toolbar-title-unsync'})
|
||||
.text('('+Messages.readonly+')'));
|
||||
var $input = $('<input>', {
|
||||
type: 'text',
|
||||
placeholder: placeholder
|
||||
|
@ -687,6 +691,7 @@ MessengerUI, Messages) {
|
|||
return true;
|
||||
});
|
||||
var save = function () {
|
||||
if (toolbar.history) { return; }
|
||||
var name = $input.val().trim();
|
||||
if (name === "") {
|
||||
name = $input.attr('placeholder');
|
||||
|
@ -717,6 +722,7 @@ MessengerUI, Messages) {
|
|||
|
||||
var displayInput = function () {
|
||||
if (toolbar.connected === false) { return; }
|
||||
if (toolbar.history) { return; }
|
||||
$input.width(Math.max(($text.width() + 10), 300)+'px');
|
||||
$text.hide();
|
||||
//$pencilIcon.css('display', 'none');
|
||||
|
@ -1273,12 +1279,14 @@ MessengerUI, Messages) {
|
|||
//checkLag(toolbar, config);
|
||||
};
|
||||
toolbar.initializing = function (/*userId*/) {
|
||||
if (toolbar.history) { return; }
|
||||
toolbar.connected = false;
|
||||
if (toolbar.spinner) {
|
||||
toolbar.spinner.text(Messages.initializing);
|
||||
}
|
||||
};
|
||||
toolbar.reconnecting = function (/*userId*/) {
|
||||
if (toolbar.history) { return; }
|
||||
toolbar.connected = false;
|
||||
if (toolbar.spinner) {
|
||||
var state = -1;
|
||||
|
@ -1342,6 +1350,27 @@ MessengerUI, Messages) {
|
|||
}
|
||||
};
|
||||
|
||||
Messages.snaphot_title = "Snapshot"; //XXX
|
||||
|
||||
toolbar.setSnapshot = function (bool) {
|
||||
toolbar.history = bool;
|
||||
toolbar.title.toggleClass('cp-toolbar-unsync', bool);
|
||||
if (bool && toolbar.spinner) {
|
||||
toolbar.spinner.text(Messages.snaphot_title);
|
||||
} else {
|
||||
kickSpinner(toolbar, config);
|
||||
}
|
||||
};
|
||||
toolbar.setHistory = function (bool) {
|
||||
toolbar.history = bool;
|
||||
toolbar.title.toggleClass('cp-toolbar-unsync', bool);
|
||||
if (bool && toolbar.spinner) {
|
||||
toolbar.spinner.text(Messages.historyText);
|
||||
} else {
|
||||
kickSpinner(toolbar, config);
|
||||
}
|
||||
};
|
||||
|
||||
// On log out, remove permanently the realtime elements of the toolbar
|
||||
Common.onLogout(function () {
|
||||
failed();
|
||||
|
|
|
@ -587,6 +587,10 @@ define([
|
|||
var setHistory = function (bool, update) {
|
||||
history = bool;
|
||||
if (!bool && update) { config.onRemote(); }
|
||||
else {
|
||||
setTimeout(cpNfInner.metadataMgr.refresh);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var displayDoc = function (doc) {
|
||||
|
@ -594,13 +598,52 @@ define([
|
|||
console.log(doc);
|
||||
};
|
||||
|
||||
var toRestore;
|
||||
var extractMetadata = function (content) {
|
||||
if (Array.isArray(content)) {
|
||||
var m = content[content.length - 1];
|
||||
if (typeof(m.metadata) === 'object') {
|
||||
// pad
|
||||
return m.metadata;
|
||||
}
|
||||
} else if (typeof(content.metadata) === 'object') {
|
||||
return content.metadata;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// Get the realtime metadata when in history mode
|
||||
var getLastMetadata = function () {
|
||||
var newContentStr = cpNfInner.chainpad.getUserDoc();
|
||||
var newContent = JSON.parse(newContentStr);
|
||||
var meta = extractMetadata(newContent);
|
||||
return meta;
|
||||
};
|
||||
var setLastMetadata = function (md) {
|
||||
var newContentStr = cpNfInner.chainpad.getAuthDoc();
|
||||
var newContent = JSON.parse(newContentStr);
|
||||
if (Array.isArray(newContent)) {
|
||||
newContent[3] = {
|
||||
metadata: md
|
||||
};
|
||||
} else {
|
||||
newContent.metadata = md;
|
||||
}
|
||||
try {
|
||||
cpNfInner.chainpad.contentUpdate(JSONSortify(newContent));
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var toRestore;
|
||||
config.onLocal = function (a, restore) {
|
||||
if (!toRestore || !restore) { return; }
|
||||
cpNfInner.chainpad.contentUpdate(toRestore);
|
||||
};
|
||||
|
||||
|
||||
config.onInit = function (info) {
|
||||
Title = common.createTitle({});
|
||||
|
||||
|
@ -620,12 +663,35 @@ define([
|
|||
/* add a history button */
|
||||
var histConfig = {
|
||||
onLocal: function () {
|
||||
// The following lines allow us to restore an old version from the debug app
|
||||
// without affecting the snapshots.
|
||||
// It's parsing, updating and stringifying text data which is not a clean way
|
||||
// to change metadata, so we're disabling it by default.
|
||||
if (window.cp_snapshots) {
|
||||
var md = Util.clone(cpNfInner.metadataMgr.getMetadata());
|
||||
var _snapshots = md.snapshots;
|
||||
var newContent = JSON.parse(toRestore);
|
||||
try {
|
||||
if (Array.isArray(newContent)) {
|
||||
newContent[3].metadata.snapshots = _snapshots;
|
||||
} else {
|
||||
newContent.metadata.snapshots = _snapshots;
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
toRestore = JSONSortify(newContent);
|
||||
}
|
||||
config.onLocal(null, true);
|
||||
},
|
||||
onRemote: config.onRemote,
|
||||
setHistory: setHistory,
|
||||
extractMetadata: extractMetadata,
|
||||
getLastMetadata: getLastMetadata, // get from authdoc
|
||||
setLastMetadata: setLastMetadata, // set to userdoc/authdoc
|
||||
applyVal: function (val) {
|
||||
toRestore = val;
|
||||
var newContent = JSON.parse(val);
|
||||
var meta = extractMetadata(newContent);
|
||||
cpNfInner.metadataMgr.updateMetadata(meta);
|
||||
displayDoc(JSON.parse(val) || {});
|
||||
},
|
||||
$toolbar: $bar,
|
||||
|
|
|
@ -128,6 +128,7 @@ define([
|
|||
if (!bool && update) {
|
||||
history.onLeaveHistory();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var main = function () {
|
||||
|
@ -251,6 +252,7 @@ define([
|
|||
history.currentObj = obj;
|
||||
history.onEnterHistory(obj);
|
||||
},
|
||||
drive: true,
|
||||
$toolbar: APP.$bar,
|
||||
};
|
||||
|
||||
|
|
|
@ -1013,7 +1013,6 @@ define([
|
|||
|
||||
var mkHelpMenu = function (framework) {
|
||||
var $toolbarContainer = $('#cp-app-kanban-container');
|
||||
$toolbarContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning());
|
||||
|
||||
var helpMenu = framework._.sfCommon.createHelpMenu(['kanban']);
|
||||
$toolbarContainer.prepend(helpMenu.menu);
|
||||
|
|
|
@ -206,7 +206,6 @@ define([
|
|||
|
||||
var mkHelpMenu = function(framework) {
|
||||
var $toolbarContainer = $('.cke_toolbox_main');
|
||||
$toolbarContainer.before(framework._.sfCommon.getBurnAfterReadingWarning());
|
||||
var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'pad']);
|
||||
$toolbarContainer.before(helpMenu.menu);
|
||||
|
||||
|
|
|
@ -428,7 +428,6 @@ define([
|
|||
|
||||
var mkHelpMenu = function (framework) {
|
||||
var $codeMirrorContainer = $('#cp-app-slide-editor-container');
|
||||
$codeMirrorContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning());
|
||||
var helpMenu = framework._.sfCommon.createHelpMenu(['text', 'slide']);
|
||||
$codeMirrorContainer.prepend(helpMenu.menu);
|
||||
|
||||
|
|
|
@ -250,7 +250,6 @@ define([
|
|||
|
||||
var mkHelpMenu = function (framework) {
|
||||
var $appContainer = $('#cp-app-whiteboard-container');
|
||||
$appContainer.prepend(framework._.sfCommon.getBurnAfterReadingWarning());
|
||||
var helpMenu = framework._.sfCommon.createHelpMenu(['whiteboard']);
|
||||
$appContainer.prepend(helpMenu.menu);
|
||||
framework._.toolbar.$drawer.append(helpMenu.button);
|
||||
|
|
Loading…
Reference in New Issue