Merge branch 'staging' into soon
This commit is contained in:
@@ -43,7 +43,8 @@
|
|||||||
"nthen": "^0.1.5",
|
"nthen": "^0.1.5",
|
||||||
"open-sans-fontface": "^1.4.2",
|
"open-sans-fontface": "^1.4.2",
|
||||||
"bootstrap-tokenfield": "^0.12.1",
|
"bootstrap-tokenfield": "^0.12.1",
|
||||||
"localforage": "^1.5.2"
|
"localforage": "^1.5.2",
|
||||||
|
"html2canvas": "^0.4.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"bootstrap": "v4.0.0-alpha.6"
|
"bootstrap": "v4.0.0-alpha.6"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ define(function() {
|
|||||||
* You can change their duration here (measured in milliseconds)
|
* You can change their duration here (measured in milliseconds)
|
||||||
*/
|
*/
|
||||||
config.notificationTimeout = 5000;
|
config.notificationTimeout = 5000;
|
||||||
|
config.disableUserlistNotifications = false;
|
||||||
|
|
||||||
config.enablePinning = true;
|
config.enablePinning = true;
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ define([
|
|||||||
])
|
])
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
h('div.cp-version-footer', "CryptPad v1.18.0 (Snallygaster)")
|
h('div.cp-version-footer', "CryptPad v1.19.0 (Tarasque)")
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -728,7 +728,7 @@ define([
|
|||||||
|
|
||||||
Pages['/profile/'] = Pages['/profile/index.html'] = function () {
|
Pages['/profile/'] = Pages['/profile/index.html'] = function () {
|
||||||
return [
|
return [
|
||||||
h('div#toolbar'),
|
h('div#cp-toolbar'),
|
||||||
h('div#container'),
|
h('div#container'),
|
||||||
loadingScreen()
|
loadingScreen()
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -102,9 +102,9 @@
|
|||||||
|
|
||||||
@category-bg: #f4f4f4;
|
@category-bg: #f4f4f4;
|
||||||
|
|
||||||
@button-bg: #3066e5;
|
@button-bg: @colortheme_sidebar-button-bg;
|
||||||
@button-alt-bg: #fff;
|
@button-alt-bg: @colortheme_sidebar-button-alt-bg;
|
||||||
@button-red-bg: #e54e4e;
|
@button-red-bg: @colortheme_sidebar-button-red-bg;
|
||||||
|
|
||||||
.unselectable () {
|
.unselectable () {
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
@colortheme_pad-bg: #1c4fa0;
|
@colortheme_pad-bg: #1c4fa0;
|
||||||
@colortheme_pad-color: #fff;
|
@colortheme_pad-color: #fff;
|
||||||
|
@colortheme_pad-toolbar-bg: #c1e7ff;
|
||||||
|
|
||||||
@colortheme_slide-bg: #e57614;
|
@colortheme_slide-bg: #e57614;
|
||||||
@colortheme_slide-color: #fff;
|
@colortheme_slide-color: #fff;
|
||||||
@@ -50,6 +51,9 @@
|
|||||||
|
|
||||||
@colortheme_poll-bg: #006304;
|
@colortheme_poll-bg: #006304;
|
||||||
@colortheme_poll-color: #fff;
|
@colortheme_poll-color: #fff;
|
||||||
|
@colortheme_poll-help-bg: #bbffbb;
|
||||||
|
@colortheme_poll-th-bg: #005bef;
|
||||||
|
@colortheme_poll-th-fg: #fff;
|
||||||
|
|
||||||
@colortheme_whiteboard-bg: #800080;
|
@colortheme_whiteboard-bg: #800080;
|
||||||
@colortheme_whiteboard-color: #fff;
|
@colortheme_whiteboard-color: #fff;
|
||||||
@@ -60,7 +64,7 @@
|
|||||||
@colortheme_file-bg: #cd2532;
|
@colortheme_file-bg: #cd2532;
|
||||||
@colortheme_file-color: #fff;
|
@colortheme_file-color: #fff;
|
||||||
|
|
||||||
@colortheme_friends-bg: #607B8D;
|
@colortheme_friends-bg: #607b8d;
|
||||||
@colortheme_friends-color: #fff;
|
@colortheme_friends-color: #fff;
|
||||||
|
|
||||||
@colortheme_default-bg: #ddd;
|
@colortheme_default-bg: #ddd;
|
||||||
@@ -75,7 +79,7 @@
|
|||||||
@colortheme_todo-bg: #7bccd1;
|
@colortheme_todo-bg: #7bccd1;
|
||||||
@colortheme_todo-color: #000;
|
@colortheme_todo-color: #000;
|
||||||
|
|
||||||
// Sidebar layout
|
// Sidebar layout (profile / settings)
|
||||||
@colortheme_sidebar-active: #fff;
|
@colortheme_sidebar-active: #fff;
|
||||||
@colortheme_sidebar-left-bg: #eee;
|
@colortheme_sidebar-left-bg: #eee;
|
||||||
@colortheme_sidebar-left-fg: #000;
|
@colortheme_sidebar-left-fg: #000;
|
||||||
@@ -83,7 +87,9 @@
|
|||||||
@colortheme_sidebar-right-bg: #fff;
|
@colortheme_sidebar-right-bg: #fff;
|
||||||
@colortheme_sidebar-right-fg: #000;
|
@colortheme_sidebar-right-fg: #000;
|
||||||
@colortheme_sidebar-description: #777;
|
@colortheme_sidebar-description: #777;
|
||||||
|
@colortheme_sidebar-button-bg: #3066e5;
|
||||||
|
@colortheme_sidebar-button-red-bg: #e54e4e;
|
||||||
|
@colortheme_sidebar-button-alt-bg: #fff;
|
||||||
|
|
||||||
@cryptpad_color_blue: #4591C4;
|
@cryptpad_color_blue: #4591C4;
|
||||||
@cryptpad_color_grey: #999999;
|
@cryptpad_color_grey: #999999;
|
||||||
|
|||||||
@@ -13,5 +13,18 @@
|
|||||||
.cp-icon-color-profile { color: @colortheme_settings-bg; }
|
.cp-icon-color-profile { color: @colortheme_settings-bg; }
|
||||||
.cp-icon-color-default { color: @colortheme_default-bg; }
|
.cp-icon-color-default { color: @colortheme_default-bg; }
|
||||||
.cp-icon-color-todo { color:@colortheme_todo-bg; }
|
.cp-icon-color-todo { color:@colortheme_todo-bg; }
|
||||||
|
|
||||||
|
.cp-border-color-pad { border-color: @colortheme_pad-bg !important; }
|
||||||
|
.cp-border-color-code { border-color: @colortheme_code-bg !important; }
|
||||||
|
.cp-border-color-slide { border-color: @colortheme_slide-bg !important; }
|
||||||
|
.cp-border-color-poll { border-color: @colortheme_poll-bg !important; }
|
||||||
|
.cp-border-color-file { border-color: @colortheme_file-bg !important; }
|
||||||
|
.cp-border-color-contacts { border-color: @colortheme_friends-bg !important; }
|
||||||
|
.cp-border-color-whiteboard { border-color: @colortheme_whiteboard-bg !important; }
|
||||||
|
.cp-border-color-drive { border-color: @colortheme_drive-bg !important; }
|
||||||
|
.cp-border-color-settings { border-color: @colortheme_settings-bg !important; }
|
||||||
|
.cp-border-color-profile { border-color: @colortheme_settings-bg !important; }
|
||||||
|
.cp-border-color-default { border-color: @colortheme_default-bg !important; }
|
||||||
|
.cp-border-color-todo { border-color:@colortheme_todo-bg !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
99
customize.dist/src/less2/include/sidebar-layout.less
Normal file
99
customize.dist/src/less2/include/sidebar-layout.less
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
@import (once) "/customize/src/less2/include/colortheme.less";
|
||||||
|
@import (once) "/customize/src/less2/include/leftside-menu.less";
|
||||||
|
|
||||||
|
@leftside-bg: @colortheme_sidebar-left-bg;
|
||||||
|
@leftside-color: @colortheme_sidebar-left-fg;
|
||||||
|
@rightside-color: @colortheme_sidebar-right-fg;
|
||||||
|
@description-color: @colortheme_sidebar-description;
|
||||||
|
|
||||||
|
@button-width: 400px;
|
||||||
|
|
||||||
|
|
||||||
|
.sidebar-layout_main() {
|
||||||
|
input[type="text"] {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
#cp-sidebarlayout-container {
|
||||||
|
font-size: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
#cp-sidebarlayout-leftside {
|
||||||
|
color: @leftside-color;
|
||||||
|
width: 250px;
|
||||||
|
background: @leftside-bg;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
.cp-sidebarlayout-categories {
|
||||||
|
flex: 1;
|
||||||
|
.cp-sidebarlayout-category {
|
||||||
|
.leftside-menu-category_main();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#cp-sidebarlayout-rightside {
|
||||||
|
flex: 1;
|
||||||
|
padding: 5px 20px;
|
||||||
|
color: @rightside-color;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
// Following rules are only in settings
|
||||||
|
.element {
|
||||||
|
label:not(.noTitle), .label {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
display: block;
|
||||||
|
color: @description-color;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
[type="text"], button {
|
||||||
|
vertical-align: middle;
|
||||||
|
height: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.inputBlock {
|
||||||
|
display: inline-flex;
|
||||||
|
width: @button-width;
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
border-radius: 0.25em 0 0 0.25em;
|
||||||
|
border: 1px solid #adadad;
|
||||||
|
border-right: 0px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
border-radius: 0 0.25em 0.25em 0;
|
||||||
|
//border: 1px solid #adadad;
|
||||||
|
border-left: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&>div {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
button.btn {
|
||||||
|
@button-bg: @colortheme_sidebar-button-bg;
|
||||||
|
@button-red-bg: @colortheme_sidebar-button-red-bg;
|
||||||
|
background-color: @button-bg;
|
||||||
|
border-color: darken(@button-bg, 10%);
|
||||||
|
color: white;
|
||||||
|
&:hover {
|
||||||
|
background-color: darken(@button-bg, 10%);
|
||||||
|
}
|
||||||
|
&.btn-danger {
|
||||||
|
background-color: @button-red-bg;
|
||||||
|
border-color: darken(@button-red-bg, 10%);
|
||||||
|
color: white;
|
||||||
|
&:hover {
|
||||||
|
background-color: darken(@button-red-bg, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,4 +33,6 @@ body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; }
|
|||||||
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
|
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
|
||||||
body.cp-app-poll { @import "../../../poll/app-poll.less"; }
|
body.cp-app-poll { @import "../../../poll/app-poll.less"; }
|
||||||
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
|
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
|
||||||
|
body.cp-app-todo { @import "../../../todo/app-todo.less"; }
|
||||||
|
body.cp-app-profile { @import "../../../profile/app-profile.less"; }
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ define(function () {
|
|||||||
out.typing = "Édition";
|
out.typing = "Édition";
|
||||||
out.initializing = "Initialisation...";
|
out.initializing = "Initialisation...";
|
||||||
out.forgotten = 'Déplacé vers la corbeille';
|
out.forgotten = 'Déplacé vers la corbeille';
|
||||||
|
out.errorState = 'Erreur critique : {0}';
|
||||||
out.lag = 'Latence';
|
out.lag = 'Latence';
|
||||||
out.readonly = 'Lecture seule';
|
out.readonly = 'Lecture seule';
|
||||||
out.anonymous = "Anonyme";
|
out.anonymous = "Anonyme";
|
||||||
@@ -56,6 +57,7 @@ define(function () {
|
|||||||
out.viewers = "lecteurs";
|
out.viewers = "lecteurs";
|
||||||
out.editor = "éditeur";
|
out.editor = "éditeur";
|
||||||
out.editors = "éditeurs";
|
out.editors = "éditeurs";
|
||||||
|
out.userlist_offline = "Vous êtes actuellement hors-ligne, la liste des utilisateurs n'est pas disponible.";
|
||||||
|
|
||||||
out.language = "Langue";
|
out.language = "Langue";
|
||||||
|
|
||||||
@@ -359,8 +361,8 @@ define(function () {
|
|||||||
out.fm_openParent = "Montrer dans le dossier";
|
out.fm_openParent = "Montrer dans le dossier";
|
||||||
out.fm_noname = "Document sans titre";
|
out.fm_noname = "Document sans titre";
|
||||||
out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?";
|
out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?";
|
||||||
out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de manière permanente ?";
|
out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?";
|
||||||
out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de manière permanente ?";
|
out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?";
|
||||||
out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?";
|
out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?";
|
||||||
out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?";
|
out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?";
|
||||||
out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?";
|
out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?";
|
||||||
@@ -397,9 +399,9 @@ define(function () {
|
|||||||
out.fc_rename = "Renommer";
|
out.fc_rename = "Renommer";
|
||||||
out.fc_open = "Ouvrir";
|
out.fc_open = "Ouvrir";
|
||||||
out.fc_open_ro = "Ouvrir (lecture seule)";
|
out.fc_open_ro = "Ouvrir (lecture seule)";
|
||||||
out.fc_delete = "Supprimer";
|
out.fc_delete = "Déplacer vers la corbeille";
|
||||||
out.fc_restore = "Restaurer";
|
out.fc_restore = "Restaurer";
|
||||||
out.fc_remove = "Supprimer définitivement";
|
out.fc_remove = "Supprimer de votre CryptDrive";
|
||||||
out.fc_empty = "Vider la corbeille";
|
out.fc_empty = "Vider la corbeille";
|
||||||
out.fc_prop = "Propriétés";
|
out.fc_prop = "Propriétés";
|
||||||
out.fc_hashtag = "Mots-clés";
|
out.fc_hashtag = "Mots-clés";
|
||||||
@@ -486,6 +488,13 @@ define(function () {
|
|||||||
out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive";
|
out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive";
|
||||||
out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles.";
|
out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles.";
|
||||||
|
|
||||||
|
out.settings_thumbnails = "Vignettes";
|
||||||
|
out.settings_disableThumbnailsAction = "Désactiver la création de vignettes dans CryptDrive";
|
||||||
|
out.settings_disableThumbnailsDescription = "Des vignettes de vos pads sont automatiquement créées et stockées dans votre navigateur. Vous pouvez désactiver cette fonctionnalité.";
|
||||||
|
out.settings_resetThumbnailsAction = "Nettoyer";
|
||||||
|
out.settings_resetThumbnailsDescription = "Nettoyer toutes les vignettes stockées dans votre navigateur.";
|
||||||
|
out.settings_resetThumbnailsDone = "Toutes les vignettes ont été effacées.";
|
||||||
|
|
||||||
out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive";
|
out.settings_importTitle = "Importer les pads récents de ce navigateur dans votre CryptDrive";
|
||||||
out.settings_import = "Importer";
|
out.settings_import = "Importer";
|
||||||
out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?";
|
out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?";
|
||||||
|
|||||||
@@ -43,9 +43,10 @@ define(function () {
|
|||||||
out.disconnected = 'Disconnected';
|
out.disconnected = 'Disconnected';
|
||||||
out.synchronizing = 'Synchronizing';
|
out.synchronizing = 'Synchronizing';
|
||||||
out.reconnecting = 'Reconnecting...';
|
out.reconnecting = 'Reconnecting...';
|
||||||
out.typing = "Typing";
|
out.typing = "Editing";
|
||||||
out.initializing = "Initializing...";
|
out.initializing = "Initializing...";
|
||||||
out.forgotten = 'Moved to the trash';
|
out.forgotten = 'Moved to the trash';
|
||||||
|
out.errorState = 'Critical error: {0}';
|
||||||
out.lag = 'Lag';
|
out.lag = 'Lag';
|
||||||
out.readonly = 'Read only';
|
out.readonly = 'Read only';
|
||||||
out.anonymous = "Anonymous";
|
out.anonymous = "Anonymous";
|
||||||
@@ -58,6 +59,7 @@ define(function () {
|
|||||||
out.viewers = "viewers";
|
out.viewers = "viewers";
|
||||||
out.editor = "editor";
|
out.editor = "editor";
|
||||||
out.editors = "editors";
|
out.editors = "editors";
|
||||||
|
out.userlist_offline = "You're currently offline, the user list is not available.";
|
||||||
|
|
||||||
out.language = "Language";
|
out.language = "Language";
|
||||||
|
|
||||||
@@ -361,8 +363,8 @@ define(function () {
|
|||||||
out.fm_openParent = "Show in folder";
|
out.fm_openParent = "Show in folder";
|
||||||
out.fm_noname = "Untitled Document";
|
out.fm_noname = "Untitled Document";
|
||||||
out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?";
|
out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?";
|
||||||
out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from the trash permanently?";
|
out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from your CryptDrive permanently?";
|
||||||
out.fm_removePermanentlyDialog = "Are you sure you want to remove that element permanently?";
|
out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?";
|
||||||
out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?";
|
out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?";
|
||||||
out.fm_removeDialog = "Are you sure you want to move {0} to the trash?";
|
out.fm_removeDialog = "Are you sure you want to move {0} to the trash?";
|
||||||
out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?";
|
out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?";
|
||||||
@@ -399,9 +401,9 @@ define(function () {
|
|||||||
out.fc_rename = "Rename";
|
out.fc_rename = "Rename";
|
||||||
out.fc_open = "Open";
|
out.fc_open = "Open";
|
||||||
out.fc_open_ro = "Open (read-only)";
|
out.fc_open_ro = "Open (read-only)";
|
||||||
out.fc_delete = "Delete";
|
out.fc_delete = "Move to trash";
|
||||||
out.fc_restore = "Restore";
|
out.fc_restore = "Restore";
|
||||||
out.fc_remove = "Delete permanently";
|
out.fc_remove = "Remove from your CryptDrive";
|
||||||
out.fc_empty = "Empty the trash";
|
out.fc_empty = "Empty the trash";
|
||||||
out.fc_prop = "Properties";
|
out.fc_prop = "Properties";
|
||||||
out.fc_hashtag = "Tags";
|
out.fc_hashtag = "Tags";
|
||||||
@@ -491,6 +493,13 @@ define(function () {
|
|||||||
out.settings_resetTipsButton = "Reset the available tips in CryptDrive";
|
out.settings_resetTipsButton = "Reset the available tips in CryptDrive";
|
||||||
out.settings_resetTipsDone = "All the tips are now visible again.";
|
out.settings_resetTipsDone = "All the tips are now visible again.";
|
||||||
|
|
||||||
|
out.settings_thumbnails = "Thumbnails";
|
||||||
|
out.settings_disableThumbnailsAction = "Disable thumbnails creation in your CryptDrive";
|
||||||
|
out.settings_disableThumbnailsDescription = "Thumbnails are automatically created and stored in your browser when you visit a new pad. You can disable this feature here.";
|
||||||
|
out.settings_resetThumbnailsAction = "Clean";
|
||||||
|
out.settings_resetThumbnailsDescription = "Clean all the pads thumbnails stored in your browser.";
|
||||||
|
out.settings_resetThumbnailsDone = "All the thumbnails have been erased.";
|
||||||
|
|
||||||
out.settings_importTitle = "Import this browser's recent pads in your CryptDrive";
|
out.settings_importTitle = "Import this browser's recent pads in your CryptDrive";
|
||||||
out.settings_import = "Import";
|
out.settings_import = "Import";
|
||||||
out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?";
|
out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cryptpad",
|
"name": "cryptpad",
|
||||||
"description": "realtime collaborative visual editor with zero knowlege server",
|
"description": "realtime collaborative visual editor with zero knowlege server",
|
||||||
"version": "1.18.0",
|
"version": "1.19.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chainpad-server": "^1.0.1",
|
"chainpad-server": "^1.0.1",
|
||||||
"express": "~4.10.1",
|
"express": "~4.10.1",
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ define([
|
|||||||
'/drive/tests.js',
|
'/drive/tests.js',
|
||||||
'/common/test.js',
|
'/common/test.js',
|
||||||
'/common/common-thumbnail.js',
|
'/common/common-thumbnail.js',
|
||||||
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb) {
|
'/common/flat-dom.js',
|
||||||
|
], function ($, Hyperjson, TextPatcher, Sortify, Cryptpad, Drive, Test, Thumb, Flat) {
|
||||||
window.Hyperjson = Hyperjson;
|
window.Hyperjson = Hyperjson;
|
||||||
window.TextPatcher = TextPatcher;
|
window.TextPatcher = TextPatcher;
|
||||||
window.Sortify = Sortify;
|
window.Sortify = Sortify;
|
||||||
@@ -236,11 +237,20 @@ define([
|
|||||||
!secret.hashData.present);
|
!secret.hashData.present);
|
||||||
}, "test support for trailing slashes in version 1 hash failed to parse");
|
}, "test support for trailing slashes in version 1 hash failed to parse");
|
||||||
|
|
||||||
|
assert(function (cb) {
|
||||||
|
var secret = Cryptpad.parsePadUrl('/invite/#/1/ilrOtygzDVoUSRpOOJrUuQ/e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=/');
|
||||||
|
var hd = secret.hashData;
|
||||||
|
cb(hd.channel === "ilrOtygzDVoUSRpOOJrUuQ" &&
|
||||||
|
hd.pubkey === "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmr=" &&
|
||||||
|
hd.type === 'invite');
|
||||||
|
}, "test support for invite urls");
|
||||||
|
|
||||||
assert(function (cb) {
|
assert(function (cb) {
|
||||||
// TODO
|
// TODO
|
||||||
return cb(true);
|
return cb(true);
|
||||||
}, "version 2 hash failed to parse correctly");
|
}, "version 2 hash failed to parse correctly");
|
||||||
|
|
||||||
|
/*
|
||||||
assert(function (cb) {
|
assert(function (cb) {
|
||||||
var getBlob = function (url, cb) {
|
var getBlob = function (url, cb) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
@@ -266,9 +276,21 @@ define([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
Drive.test(assert);
|
Drive.test(assert);
|
||||||
|
|
||||||
|
assert(function (cb) {
|
||||||
|
// extract dom elements into a flattened JSON representation
|
||||||
|
var flat = Flat.fromDOM(document.body);
|
||||||
|
// recreate a _mostly_ equivalent DOM
|
||||||
|
var dom = Flat.toDOM(flat);
|
||||||
|
// assume we don't care about comments
|
||||||
|
var bodyText = document.body.outerHTML.replace(/<!\-\-[\s\S]*?\-\->/g, '');
|
||||||
|
// check for equality
|
||||||
|
cb(dom.outerHTML === bodyText);
|
||||||
|
});
|
||||||
|
|
||||||
var swap = function (str, dict) {
|
var swap = function (str, dict) {
|
||||||
return str.replace(/\{\{(.*?)\}\}/g, function (all, key) {
|
return str.replace(/\{\{(.*?)\}\}/g, function (all, key) {
|
||||||
return typeof dict[key] !== 'undefined'? dict[key] : all;
|
return typeof dict[key] !== 'undefined'? dict[key] : all;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.loading-hidden { display: none; }
|
.loading-hidden { display: none; }
|
||||||
#editor1 { display: none; }
|
#editor1 { display: none; }
|
||||||
|
.html2canvas-container { width: 3000px !important; height: 3000px !important; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="cp-app-code">
|
<body class="cp-app-code">
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ define([
|
|||||||
$codeMirror.addClass('cp-app-code-fullpage');
|
$codeMirror.addClass('cp-app-code-fullpage');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var isVisible = function () {
|
||||||
|
return $previewContainer.is(':visible');
|
||||||
|
};
|
||||||
|
|
||||||
framework.onReady(function () {
|
framework.onReady(function () {
|
||||||
// add the splitter
|
// add the splitter
|
||||||
var splitter = $('<div>', {
|
var splitter = $('<div>', {
|
||||||
@@ -184,7 +188,8 @@ define([
|
|||||||
return {
|
return {
|
||||||
forceDraw: forceDrawPreview,
|
forceDraw: forceDrawPreview,
|
||||||
draw: drawPreview,
|
draw: drawPreview,
|
||||||
modeChange: modeChange
|
modeChange: modeChange,
|
||||||
|
isVisible: isVisible
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -317,6 +322,17 @@ define([
|
|||||||
framework.start();
|
framework.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var getThumbnailContainer = function () {
|
||||||
|
var $preview = $('#cp-app-code-preview-content');
|
||||||
|
var $codeMirror = $('.CodeMirror');
|
||||||
|
if ($preview.length && $preview.is(':visible')) {
|
||||||
|
return $preview[0];
|
||||||
|
}
|
||||||
|
if ($codeMirror.length) {
|
||||||
|
return $codeMirror[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var main = function () {
|
var main = function () {
|
||||||
var CodeMirror;
|
var CodeMirror;
|
||||||
var editor;
|
var editor;
|
||||||
@@ -327,7 +343,19 @@ define([
|
|||||||
|
|
||||||
Framework.create({
|
Framework.create({
|
||||||
toolbarContainer: '#cme_toolbox',
|
toolbarContainer: '#cme_toolbox',
|
||||||
contentContainer: '#cp-app-code-editor'
|
contentContainer: '#cp-app-code-editor',
|
||||||
|
thumbnail: {
|
||||||
|
getContainer: getThumbnailContainer,
|
||||||
|
filter: function (el, before) {
|
||||||
|
if (before) {
|
||||||
|
$(el).parents().css('overflow', 'visible');
|
||||||
|
$(el).css('max-height', Math.max(600, $(el).width()) + 'px');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(el).parents().css('overflow', '');
|
||||||
|
$(el).css('max-height', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
}, waitFor(function (fw) { framework = fw; }));
|
}, waitFor(function (fw) { framework = fw; }));
|
||||||
|
|
||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
|
|||||||
@@ -296,18 +296,11 @@ define([
|
|||||||
|
|
||||||
if (!Thumb.isSupportedType(file.type)) { return finish(); }
|
if (!Thumb.isSupportedType(file.type)) { return finish(); }
|
||||||
// make a resized thumbnail from the image..
|
// make a resized thumbnail from the image..
|
||||||
Thumb.fromBlob(file, function (e, thumb_blob) {
|
Thumb.fromBlob(file, function (e, thumb64) {
|
||||||
if (e) { console.error(e); }
|
if (e) { console.error(e); }
|
||||||
if (!thumb_blob) { return finish(); }
|
if (!thumb64) { return finish(); }
|
||||||
|
thumb = thumb64;
|
||||||
blobToArrayBuffer(thumb_blob, function (e, buffer) {
|
finish();
|
||||||
if (e) {
|
|
||||||
console.error(e);
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
thumb = arrayBufferToString(buffer);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -526,7 +526,8 @@ define([
|
|||||||
// them.
|
// them.
|
||||||
var win;
|
var win;
|
||||||
$('.tippy-popper').each(function (i, el) {
|
$('.tippy-popper').each(function (i, el) {
|
||||||
win = win || $('#pad-iframe')[0].contentWindow;
|
win = win || $('#pad-iframe').length? $('#pad-iframe')[0].contentWindow: undefined;
|
||||||
|
if (!win) { return; }
|
||||||
if (win.$('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
|
if (win.$('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
|
||||||
el.remove();
|
el.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
define([
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/common/common-util.js',
|
||||||
|
'/common/visible.js',
|
||||||
|
'/common/common-hash.js',
|
||||||
|
'/file/file-crypto.js',
|
||||||
|
'/bower_components/localforage/dist/localforage.min.js',
|
||||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
], function () {
|
], function ($, Util, Visible, Hash, FileCrypto, localForage) {
|
||||||
var Nacl = window.nacl;
|
var Nacl = window.nacl;
|
||||||
var Thumb = {
|
var Thumb = {
|
||||||
dimension: 100,
|
dimension: 100,
|
||||||
|
padDimension: 200,
|
||||||
|
UPDATE_INTERVAL: 60000,
|
||||||
|
UPDATE_FIRST: 5000
|
||||||
};
|
};
|
||||||
|
|
||||||
var supportedTypes = [
|
var supportedTypes = [
|
||||||
@@ -11,7 +20,8 @@ define([
|
|||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/jpg',
|
'image/jpg',
|
||||||
'image/gif',
|
'image/gif',
|
||||||
'video/'
|
'video/',
|
||||||
|
'application/pdf'
|
||||||
];
|
];
|
||||||
|
|
||||||
Thumb.isSupportedType = function (type) {
|
Thumb.isSupportedType = function (type) {
|
||||||
@@ -41,22 +51,32 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var getResizedDimensions = function (img, type) {
|
var getResizedDimensions = Thumb.getResizedDimensions = function (img, type) {
|
||||||
var h = type === 'video' ? img.videoHeight : img.height;
|
var h = type === 'video' ? img.videoHeight : img.height;
|
||||||
var w = type === 'video' ? img.videoWidth : img.width;
|
var w = type === 'video' ? img.videoWidth : img.width;
|
||||||
|
|
||||||
var dim = Thumb.dimension;
|
var dim = type === 'pad' ? Thumb.padDimension : Thumb.dimension;
|
||||||
|
|
||||||
// if the image is too small, don't bother making a thumbnail
|
// if the image is too small, don't bother making a thumbnail
|
||||||
if (h <= dim && w <= dim) { return null; }
|
/*if (h <= dim && w <= dim) {
|
||||||
|
return {
|
||||||
|
x: Math.floor((dim - w) / 2),
|
||||||
|
w: w,
|
||||||
|
y: Math.floor((dim - h) / 2),
|
||||||
|
h : h
|
||||||
|
};
|
||||||
|
}*/
|
||||||
|
|
||||||
// the image is taller than it is wide, so scale to that.
|
// the image is taller than it is wide, so scale to that.
|
||||||
var r = dim / (h > w? h: w); // ratio
|
var r = dim / (h > w? h: w); // ratio
|
||||||
|
if (h <= dim && w <= dim) { r = 1; }
|
||||||
|
|
||||||
var d;
|
var d;
|
||||||
if (h > w) {
|
if (h > w) {
|
||||||
var newW = Math.floor(w*r);
|
var newW = Math.floor(w*r);
|
||||||
d = Math.floor((dim - newW) / 2);
|
d = Math.floor((dim - newW) / 2);
|
||||||
return {
|
return {
|
||||||
|
dim: dim,
|
||||||
x: d,
|
x: d,
|
||||||
w: newW,
|
w: newW,
|
||||||
y: 0,
|
y: 0,
|
||||||
@@ -66,6 +86,7 @@ define([
|
|||||||
var newH = Math.floor(h*r);
|
var newH = Math.floor(h*r);
|
||||||
d = Math.floor((dim - newH) / 2);
|
d = Math.floor((dim - newH) / 2);
|
||||||
return {
|
return {
|
||||||
|
dim: dim,
|
||||||
x: 0,
|
x: 0,
|
||||||
w: dim,
|
w: dim,
|
||||||
y: d,
|
y: d,
|
||||||
@@ -76,18 +97,16 @@ define([
|
|||||||
|
|
||||||
// assumes that your canvas is square
|
// assumes that your canvas is square
|
||||||
// nodeback returning blob
|
// nodeback returning blob
|
||||||
Thumb.fromCanvas = Thumb.fromImage = function (canvas, D, cb) {
|
Thumb.fromCanvas = function (canvas, D, cb) {
|
||||||
var c2 = document.createElement('canvas');
|
var c2 = document.createElement('canvas');
|
||||||
if (!D) { return void cb('TOO_SMALL'); }
|
if (!D) { return void cb('ERROR'); }
|
||||||
|
|
||||||
c2.width = Thumb.dimension;
|
c2.width = D.dim;
|
||||||
c2.height = Thumb.dimension;
|
c2.height = D.dim;
|
||||||
|
|
||||||
var ctx = c2.getContext('2d');
|
var ctx = c2.getContext('2d');
|
||||||
ctx.drawImage(canvas, D.x, D.y, D.w, D.h);
|
ctx.drawImage(canvas, D.x, D.y, D.w, D.h);
|
||||||
c2.toBlob(function (blob) {
|
cb(void 0, c2.toDataURL());
|
||||||
cb(void 0, blob);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Thumb.fromImageBlob = function (blob, cb) {
|
Thumb.fromImageBlob = function (blob, cb) {
|
||||||
@@ -96,10 +115,7 @@ define([
|
|||||||
|
|
||||||
img.onload = function () {
|
img.onload = function () {
|
||||||
var D = getResizedDimensions(img, 'image');
|
var D = getResizedDimensions(img, 'image');
|
||||||
Thumb.fromImage(img, D, function (err, t) {
|
Thumb.fromCanvas(img, D, cb);
|
||||||
if (err === 'TOO_SMALL') { return void cb(void 0, blob); }
|
|
||||||
cb(err, t);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
img.onerror = function () {
|
img.onerror = function () {
|
||||||
cb('ERROR');
|
cb('ERROR');
|
||||||
@@ -123,15 +139,138 @@ define([
|
|||||||
cb('ERROR');
|
cb('ERROR');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Thumb.fromPdfBlob = function (blob, cb) {
|
||||||
|
require.config({paths: {'pdfjs-dist': '/common/pdfjs'}});
|
||||||
|
require(['pdfjs-dist/build/pdf'], function (PDFJS) {
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var makeThumb = function (page) {
|
||||||
|
var vp = page.getViewport(1);
|
||||||
|
var canvas = document.createElement("canvas");
|
||||||
|
canvas.width = canvas.height = Thumb.dimension;
|
||||||
|
var scale = Math.min(canvas.width / vp.width, canvas.height / vp.height);
|
||||||
|
canvas.width = Math.floor(vp.width * scale);
|
||||||
|
canvas.height = Math.floor(vp.height * scale);
|
||||||
|
return page.render({
|
||||||
|
canvasContext: canvas.getContext("2d"),
|
||||||
|
viewport: page.getViewport(scale)
|
||||||
|
}).promise.then(function () {
|
||||||
|
return canvas;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
PDFJS.getDocument(url).promise
|
||||||
|
.then(function (doc) {
|
||||||
|
return doc.getPage(1).then(makeThumb).then(function (canvas) {
|
||||||
|
var D = getResizedDimensions(canvas, 'pdf');
|
||||||
|
Thumb.fromCanvas(canvas, D, cb);
|
||||||
|
});
|
||||||
|
}).catch(function () {
|
||||||
|
cb('ERROR');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
Thumb.fromBlob = function (blob, cb) {
|
Thumb.fromBlob = function (blob, cb) {
|
||||||
if (blob.type.indexOf('video/') !== -1) {
|
if (blob.type.indexOf('video/') !== -1) {
|
||||||
return void Thumb.fromVideoBlob(blob, cb);
|
return void Thumb.fromVideoBlob(blob, cb);
|
||||||
}
|
}
|
||||||
|
if (blob.type.indexOf('application/pdf') !== -1) {
|
||||||
|
return void Thumb.fromPdfBlob(blob, cb);
|
||||||
|
}
|
||||||
Thumb.fromImageBlob(blob, cb);
|
Thumb.fromImageBlob(blob, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
Thumb.fromVideo = function (video, cb) {
|
window.html2canvas = undefined;
|
||||||
cb = cb; // WIP
|
Thumb.fromDOM = function (opts, cb) {
|
||||||
|
var element = opts.getContainer();
|
||||||
|
var todo = function () {
|
||||||
|
if (opts.filter) { opts.filter(element, true); }
|
||||||
|
window.html2canvas(element, {
|
||||||
|
allowTaint: true,
|
||||||
|
onrendered: function (canvas) {
|
||||||
|
if (opts.filter) { opts.filter(element, false); }
|
||||||
|
var D = getResizedDimensions(canvas, 'pad');
|
||||||
|
Thumb.fromCanvas(canvas, D, cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (window.html2canvas) { return void todo(); }
|
||||||
|
require(['/bower_components/html2canvas/build/html2canvas.min.js'], todo);
|
||||||
|
};
|
||||||
|
|
||||||
|
Thumb.initPadThumbnails = function (opts) {
|
||||||
|
if (!opts.href || !opts.getContent) {
|
||||||
|
throw new Error("href and getContent are needed for thumbnails");
|
||||||
|
}
|
||||||
|
var oldThumbnailState;
|
||||||
|
var mkThumbnail = function () {
|
||||||
|
var content = opts.getContent();
|
||||||
|
if (content === oldThumbnailState) { return; }
|
||||||
|
Thumb.fromDOM(opts, function (err, b64) {
|
||||||
|
oldThumbnailState = content;
|
||||||
|
Thumb.setPadThumbnail(opts.href, b64);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
|
||||||
|
var to;
|
||||||
|
var tUntil;
|
||||||
|
var interval = function () {
|
||||||
|
tUntil = nafa();
|
||||||
|
if (tUntil) {
|
||||||
|
window.clearTimeout(to);
|
||||||
|
to = window.setTimeout(interval, tUntil+1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
to = window.setTimeout(interval, Thumb.UPDATE_INTERVAL+1);
|
||||||
|
};
|
||||||
|
Visible.onChange(function (v) {
|
||||||
|
if (v) {
|
||||||
|
window.clearTimeout(to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
interval();
|
||||||
|
});
|
||||||
|
if (!Visible.currently()) { to = window.setTimeout(interval, Thumb.UPDATE_FIRST); }
|
||||||
|
};
|
||||||
|
|
||||||
|
var addThumbnail = function (err, thumb, $span, cb) {
|
||||||
|
var img = new Image();
|
||||||
|
img.src = thumb.slice(0,5) === 'data:' ? thumb : 'data:;base64,'+thumb;
|
||||||
|
$span.find('.cp-icon').hide();
|
||||||
|
$span.prepend(img);
|
||||||
|
cb($(img));
|
||||||
|
};
|
||||||
|
Thumb.setPadThumbnail = function (href, b64, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var k ='thumbnail-' + href;
|
||||||
|
localForage.setItem(k, b64, cb);
|
||||||
|
};
|
||||||
|
Thumb.displayThumbnail = function (href, $container, cb) {
|
||||||
|
cb = cb || function () {};
|
||||||
|
var parsed = Hash.parsePadUrl(href);
|
||||||
|
var k ='thumbnail-' + href;
|
||||||
|
var whenNewThumb = function () {
|
||||||
|
var secret = Hash.getSecrets('file', parsed.hash);
|
||||||
|
var hexFileName = Util.base64ToHex(secret.channel);
|
||||||
|
var src = Hash.getBlobPathFromHex(hexFileName);
|
||||||
|
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
||||||
|
var key = Nacl.util.decodeBase64(cryptKey);
|
||||||
|
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
|
||||||
|
if (!metadata.thumbnail) {
|
||||||
|
return void localForage.setItem(k, 'EMPTY');
|
||||||
|
}
|
||||||
|
localForage.setItem(k, metadata.thumbnail, function (err) {
|
||||||
|
addThumbnail(err, metadata.thumbnail, $container, cb);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
localForage.getItem(k, function (err, v) {
|
||||||
|
if (!v && parsed.type === 'file') {
|
||||||
|
// We can only create thumbnails for files here since we can't easily decrypt pads
|
||||||
|
return void whenNewThumb();
|
||||||
|
}
|
||||||
|
if (!v) { return; }
|
||||||
|
if (v === 'EMPTY') { return; }
|
||||||
|
addThumbnail(err, v, $container, cb);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return Thumb;
|
return Thumb;
|
||||||
|
|||||||
@@ -45,9 +45,7 @@ define([
|
|||||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||||
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
|
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
|
||||||
if (mediaMap[src]) {
|
if (mediaMap[src]) {
|
||||||
mediaMap[src].forEach(function (n) {
|
mt += mediaMap[src];
|
||||||
mt += n.outerHTML;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
mt += '</media-tag>';
|
mt += '</media-tag>';
|
||||||
return mt;
|
return mt;
|
||||||
@@ -129,6 +127,7 @@ define([
|
|||||||
|
|
||||||
var domFromHTML = function (html) {
|
var domFromHTML = function (html) {
|
||||||
var Dom = new DOMParser().parseFromString(html, "text/html");
|
var Dom = new DOMParser().parseFromString(html, "text/html");
|
||||||
|
Dom.normalize();
|
||||||
removeForbiddenTags(Dom.body);
|
removeForbiddenTags(Dom.body);
|
||||||
removeListeners(Dom.body);
|
removeListeners(Dom.body);
|
||||||
return Dom;
|
return Dom;
|
||||||
@@ -167,11 +166,7 @@ define([
|
|||||||
|
|
||||||
var unsafe_newHtmlFixed = newHtml.replace(pattern, function (all, tag, src) {
|
var unsafe_newHtmlFixed = newHtml.replace(pattern, function (all, tag, src) {
|
||||||
var mt = tag;
|
var mt = tag;
|
||||||
if (mediaMap[src]) {
|
if (mediaMap[src]) { mt += mediaMap[src]; }
|
||||||
mediaMap[src].forEach(function (n) {
|
|
||||||
mt += n.outerHTML;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return mt + '</media-tag>';
|
return mt + '</media-tag>';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -179,6 +174,7 @@ define([
|
|||||||
var $div = $('<div>', {id: id}).append(safe_newHtmlFixed);
|
var $div = $('<div>', {id: id}).append(safe_newHtmlFixed);
|
||||||
|
|
||||||
var Dom = domFromHTML($('<div>').append($div).html());
|
var Dom = domFromHTML($('<div>').append($div).html());
|
||||||
|
$content[0].normalize();
|
||||||
var oldDom = domFromHTML($content[0].outerHTML);
|
var oldDom = domFromHTML($content[0].outerHTML);
|
||||||
var patch = makeDiff(oldDom, Dom, id);
|
var patch = makeDiff(oldDom, Dom, id);
|
||||||
if (typeof(patch) === 'string') {
|
if (typeof(patch) === 'string') {
|
||||||
@@ -191,9 +187,11 @@ define([
|
|||||||
var observer = new MutationObserver(function(mutations) {
|
var observer = new MutationObserver(function(mutations) {
|
||||||
mutations.forEach(function(mutation) {
|
mutations.forEach(function(mutation) {
|
||||||
if (mutation.type === 'childList') {
|
if (mutation.type === 'childList') {
|
||||||
//console.log(el.outerHTML);
|
var list_values = [].slice.call(mutation.target.children)
|
||||||
var list_values = [].slice.call(el.children);
|
.map(function (el) { return el.outerHTML; })
|
||||||
mediaMap[el.getAttribute('src')] = list_values;
|
.join('');
|
||||||
|
mediaMap[mutation.target.getAttribute('src')] = list_values;
|
||||||
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
83
www/common/flat-dom.js
Normal file
83
www/common/flat-dom.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
define([], function () {
|
||||||
|
var Flat = {};
|
||||||
|
|
||||||
|
var slice = function (coll) {
|
||||||
|
return Array.prototype.slice.call(coll);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getAttrs = function (el) {
|
||||||
|
var i = 0;
|
||||||
|
var l = el.attributes.length;
|
||||||
|
var attr;
|
||||||
|
var data = {};
|
||||||
|
for (;i < l;i++) {
|
||||||
|
attr = el.attributes[i];
|
||||||
|
if (attr.name && attr.value) { data[attr.name] = attr.value; }
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
var identity = function (x) { return x; };
|
||||||
|
Flat.fromDOM = function (dom) {
|
||||||
|
var data = {
|
||||||
|
map: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
var i = 1; // start from 1 so we're always truthey
|
||||||
|
var uid = function () { return i++; };
|
||||||
|
|
||||||
|
var process = function (el) {
|
||||||
|
var id;
|
||||||
|
if (!el.tagName && el.nodeType === Node.TEXT_NODE) {
|
||||||
|
id = uid();
|
||||||
|
data.map[id] = el.textContent;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
if (!el || !el.attributes) { return void console.error(el); }
|
||||||
|
id = uid();
|
||||||
|
data.map[id] = [
|
||||||
|
el.tagName,
|
||||||
|
getAttrs(el),
|
||||||
|
slice(el.childNodes).map(function (e) {
|
||||||
|
return process(e);
|
||||||
|
}).filter(identity)
|
||||||
|
];
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
data.root = process(dom);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
Flat.toDOM = function (data) {
|
||||||
|
var visited = {};
|
||||||
|
var process = function (key) {
|
||||||
|
if (!key) { return; } // ignore falsey keys
|
||||||
|
if (visited[key]) {
|
||||||
|
// TODO handle this more gracefully.
|
||||||
|
throw new Error('duplicate id or loop detected');
|
||||||
|
}
|
||||||
|
visited[key] = true; // mark paths as visited.
|
||||||
|
|
||||||
|
var hj = data.map[key];
|
||||||
|
if (typeof(hj) === 'string') { return document.createTextNode(hj); }
|
||||||
|
if (typeof(hj) === 'undefined') { return; }
|
||||||
|
if (!Array.isArray(hj)) { console.error(hj); throw new Error('expected array'); }
|
||||||
|
|
||||||
|
var e = document.createElement(hj[0]);
|
||||||
|
for (var x in hj[1]) { e.setAttribute(x, hj[1][x]); }
|
||||||
|
var child;
|
||||||
|
for (var i = 0; i < hj[2].length; i++) {
|
||||||
|
child = process(hj[2][i]);
|
||||||
|
if (child) {
|
||||||
|
e.appendChild(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
|
||||||
|
return process(data.root);
|
||||||
|
};
|
||||||
|
|
||||||
|
return Flat;
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ define([
|
|||||||
'/common/sframe-common.js',
|
'/common/sframe-common.js',
|
||||||
'/customize/messages.js',
|
'/customize/messages.js',
|
||||||
'/common/common-util.js',
|
'/common/common-util.js',
|
||||||
|
'/common/common-thumbnail.js',
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
|
|
||||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
@@ -27,6 +28,7 @@ define([
|
|||||||
SFCommon,
|
SFCommon,
|
||||||
Messages,
|
Messages,
|
||||||
Util,
|
Util,
|
||||||
|
Thumb,
|
||||||
AppConfig)
|
AppConfig)
|
||||||
{
|
{
|
||||||
var SaveAs = window.saveAs;
|
var SaveAs = window.saveAs;
|
||||||
@@ -264,6 +266,20 @@ define([
|
|||||||
|
|
||||||
Cryptpad.removeLoadingScreen(emitResize);
|
Cryptpad.removeLoadingScreen(emitResize);
|
||||||
|
|
||||||
|
var privateDat = cpNfInner.metadataMgr.getPrivateData();
|
||||||
|
if (options.thumbnail && privateDat.thumbnails) {
|
||||||
|
var hash = privateDat.availableHashes.editHash ||
|
||||||
|
privateDat.availableHashes.viewHash;
|
||||||
|
if (hash) {
|
||||||
|
options.thumbnail.href = privateDat.pathname + '#' + hash;
|
||||||
|
options.thumbnail.getContent = function () {
|
||||||
|
if (!cpNfInner.chainpad) { return; }
|
||||||
|
return cpNfInner.chainpad.getUserDoc();
|
||||||
|
};
|
||||||
|
Thumb.initPadThumbnails(options.thumbnail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (newPad) {
|
if (newPad) {
|
||||||
common.openTemplatePicker();
|
common.openTemplatePicker();
|
||||||
}
|
}
|
||||||
@@ -362,10 +378,7 @@ define([
|
|||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
cpNfInner = common.startRealtime({
|
cpNfInner = common.startRealtime({
|
||||||
// really basic operational transform
|
// really basic operational transform
|
||||||
transformFunction: options.transformFunction || JsonOT.validate,
|
transformFunction: options.transformFunction || JsonOT.transform,
|
||||||
|
|
||||||
// This one causes a big mess.
|
|
||||||
//patchTransformer: options.patchTransformer || JsonOT.patchTransformer,
|
|
||||||
|
|
||||||
// cryptpad debug logging (default is 1)
|
// cryptpad debug logging (default is 1)
|
||||||
// logLevel: 0,
|
// logLevel: 0,
|
||||||
@@ -399,6 +412,7 @@ define([
|
|||||||
|
|
||||||
textPatcher = TextPatcher.create({ realtime: cpNfInner.chainpad });
|
textPatcher = TextPatcher.create({ realtime: cpNfInner.chainpad });
|
||||||
|
|
||||||
|
var infiniteSpinnerModal = false;
|
||||||
window.setInterval(function () {
|
window.setInterval(function () {
|
||||||
if (state === STATE.DISCONNECTED) { return; }
|
if (state === STATE.DISCONNECTED) { return; }
|
||||||
var l;
|
var l;
|
||||||
@@ -409,13 +423,15 @@ define([
|
|||||||
}
|
}
|
||||||
if (l.lag < badStateTimeout) { return; }
|
if (l.lag < badStateTimeout) { return; }
|
||||||
|
|
||||||
if (state === STATE.INFINITE_SPINNER) { return; }
|
if (infiniteSpinnerModal) { return; }
|
||||||
|
infiniteSpinnerModal = true;
|
||||||
stateChange(STATE.INFINITE_SPINNER);
|
stateChange(STATE.INFINITE_SPINNER);
|
||||||
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
|
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
|
||||||
if (!yes) { return; }
|
if (!yes) { return; }
|
||||||
common.gotoURL();
|
common.gotoURL();
|
||||||
});
|
});
|
||||||
cpNfInner.chainpad.onSettle(function () {
|
cpNfInner.chainpad.onSettle(function () {
|
||||||
|
infiniteSpinnerModal = false;
|
||||||
Cryptpad.findCancelButton().click();
|
Cryptpad.findCancelButton().click();
|
||||||
stateChange(STATE.READY);
|
stateChange(STATE.READY);
|
||||||
onRemote();
|
onRemote();
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ define(['/common/requireconfig.js'], function (RequireConfig) {
|
|||||||
var mkFakeStore = function () {
|
var mkFakeStore = function () {
|
||||||
var fakeStorage = {
|
var fakeStorage = {
|
||||||
getItem: function (k) { return fakeStorage[k]; },
|
getItem: function (k) { return fakeStorage[k]; },
|
||||||
setItem: function (k, v) { fakeStorage[k] = v; return v; }
|
setItem: function (k, v) { fakeStorage[k] = v; return v; },
|
||||||
|
removeItem: function (k) { delete fakeStorage[k]; }
|
||||||
};
|
};
|
||||||
return fakeStorage;
|
return fakeStorage;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -685,7 +685,9 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var ready = false;
|
||||||
realtimeOptions.onReady = function (info) {
|
realtimeOptions.onReady = function (info) {
|
||||||
|
if (ready) { return; }
|
||||||
// create your patcher
|
// create your patcher
|
||||||
if (realtime !== info.realtime) {
|
if (realtime !== info.realtime) {
|
||||||
realtime = rt.realtime = info.realtime;
|
realtime = rt.realtime = info.realtime;
|
||||||
@@ -709,6 +711,7 @@ define([
|
|||||||
DeepProxy.checkLocalChange(proxy, onLocal);
|
DeepProxy.checkLocalChange(proxy, onLocal);
|
||||||
|
|
||||||
initializing = false;
|
initializing = false;
|
||||||
|
ready = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
realtimeOptions.onAbort = function (info) {
|
realtimeOptions.onAbort = function (info) {
|
||||||
|
|||||||
@@ -249,18 +249,11 @@ define([
|
|||||||
|
|
||||||
if (!Thumb.isSupportedType(file.type)) { return finish(); }
|
if (!Thumb.isSupportedType(file.type)) { return finish(); }
|
||||||
// make a resized thumbnail from the image..
|
// make a resized thumbnail from the image..
|
||||||
Thumb.fromBlob(file, function (e, thumb_blob) {
|
Thumb.fromBlob(file, function (e, thumb64) {
|
||||||
if (e) { console.error(e); }
|
if (e) { console.error(e); }
|
||||||
if (!thumb_blob) { return finish(); }
|
if (!thumb64) { return finish(); }
|
||||||
|
thumb = thumb64;
|
||||||
blobToArrayBuffer(thumb_blob, function (e, buffer) {
|
finish();
|
||||||
if (e) {
|
|
||||||
console.error(e);
|
|
||||||
return finish();
|
|
||||||
}
|
|
||||||
thumb = arrayBufferToString(buffer);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ define([
|
|||||||
'/api/config',
|
'/api/config',
|
||||||
'/common/cryptpad-common.js',
|
'/common/cryptpad-common.js',
|
||||||
'/common/common-util.js',
|
'/common/common-util.js',
|
||||||
'/common/common-hash.js',
|
|
||||||
'/common/media-tag.js',
|
'/common/media-tag.js',
|
||||||
'/common/tippy.min.js',
|
'/common/tippy.min.js',
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
'/file/file-crypto.js',
|
|
||||||
'/bower_components/localforage/dist/localforage.min.js',
|
|
||||||
|
|
||||||
'/bower_components/tweetnacl/nacl-fast.min.js',
|
|
||||||
'css!/common/tippy.css',
|
'css!/common/tippy.css',
|
||||||
], function ($, Config, Cryptpad, Util, Hash, MediaTag, Tippy, AppConfig, FileCrypto, localForage) {
|
], function ($, Config, Cryptpad, Util, MediaTag, Tippy, AppConfig) {
|
||||||
var UI = {};
|
var UI = {};
|
||||||
var Messages = Cryptpad.Messages;
|
var Messages = Cryptpad.Messages;
|
||||||
var Nacl = window.nacl;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requirements from cryptpad-common.js
|
* Requirements from cryptpad-common.js
|
||||||
@@ -33,40 +28,6 @@ define([
|
|||||||
* - createDropdown
|
* - createDropdown
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var addThumbnail = function (err, thumb, $span, cb) {
|
|
||||||
var img = new Image();
|
|
||||||
img.src = 'data:;base64,'+thumb;
|
|
||||||
$span.find('.cp-icon').hide();
|
|
||||||
$span.prepend(img);
|
|
||||||
cb($(img));
|
|
||||||
};
|
|
||||||
UI.displayThumbnail = function (href, $container, cb) {
|
|
||||||
cb = cb || $.noop;
|
|
||||||
var parsed = Hash.parsePadUrl(href);
|
|
||||||
if (parsed.type !== 'file') { return; }
|
|
||||||
var k ='thumbnail-' + href;
|
|
||||||
var whenNewThumb = function () {
|
|
||||||
var secret = Hash.getSecrets('file', parsed.hash);
|
|
||||||
var hexFileName = Util.base64ToHex(secret.channel);
|
|
||||||
var src = Hash.getBlobPathFromHex(hexFileName);
|
|
||||||
var cryptKey = secret.keys && secret.keys.fileKeyStr;
|
|
||||||
var key = Nacl.util.decodeBase64(cryptKey);
|
|
||||||
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
|
|
||||||
if (!metadata.thumbnail) {
|
|
||||||
return void localForage.setItem(k, 'EMPTY');
|
|
||||||
}
|
|
||||||
localForage.setItem(k, metadata.thumbnail, function (err) {
|
|
||||||
addThumbnail(err, metadata.thumbnail, $container, cb);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
localForage.getItem(k, function (err, v) {
|
|
||||||
if (!v) { return void whenNewThumb(); }
|
|
||||||
if (v === 'EMPTY') { return; }
|
|
||||||
addThumbnail(err, v, $container, cb);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
UI.updateTags = function (common, href) {
|
UI.updateTags = function (common, href) {
|
||||||
var sframeChan = common.getSframeChannel();
|
var sframeChan = common.getSframeChannel();
|
||||||
sframeChan.query('Q_TAGS_GET', href || null, function (err, res) {
|
sframeChan.query('Q_TAGS_GET', href || null, function (err, res) {
|
||||||
@@ -330,6 +291,7 @@ define([
|
|||||||
$img.attr('src', src);
|
$img.attr('src', src);
|
||||||
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
|
$img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
|
||||||
UI.displayMediatagImage(Common, $img, function (err, $image, img) {
|
UI.displayMediatagImage(Common, $img, function (err, $image, img) {
|
||||||
|
if (err) { return void console.error(err); }
|
||||||
var w = img.width;
|
var w = img.width;
|
||||||
var h = img.height;
|
var h = img.height;
|
||||||
if (w>h) {
|
if (w>h) {
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ define([
|
|||||||
settings: proxy.settings || {},
|
settings: proxy.settings || {},
|
||||||
isPresent: parsed.hashData && parsed.hashData.present,
|
isPresent: parsed.hashData && parsed.hashData.present,
|
||||||
isEmbed: parsed.hashData && parsed.hashData.embed,
|
isEmbed: parsed.hashData && parsed.hashData.embed,
|
||||||
|
thumbnails: !((proxy.settings || {}).general || {}).disableThumbnails,
|
||||||
accounts: {
|
accounts: {
|
||||||
donateURL: Cryptpad.donateURL,
|
donateURL: Cryptpad.donateURL,
|
||||||
upgradeURL: Cryptpad.upgradeURL
|
upgradeURL: Cryptpad.upgradeURL
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ define([
|
|||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
'/common/cryptpad-common.js',
|
'/common/cryptpad-common.js',
|
||||||
'/common/common-realtime.js',
|
'/common/common-realtime.js',
|
||||||
'/common/common-util.js'
|
'/common/common-util.js',
|
||||||
|
'/common/common-thumbnail.js'
|
||||||
], function (
|
], function (
|
||||||
$,
|
$,
|
||||||
nThen,
|
nThen,
|
||||||
@@ -30,7 +31,8 @@ define([
|
|||||||
AppConfig,
|
AppConfig,
|
||||||
Cryptpad,
|
Cryptpad,
|
||||||
CommonRealtime,
|
CommonRealtime,
|
||||||
Util
|
Util,
|
||||||
|
Thumb
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// Chainpad Netflux Inner
|
// Chainpad Netflux Inner
|
||||||
@@ -80,7 +82,9 @@ define([
|
|||||||
funcs.createButton = callWithCommon(UI.createButton);
|
funcs.createButton = callWithCommon(UI.createButton);
|
||||||
funcs.createUsageBar = callWithCommon(UI.createUsageBar);
|
funcs.createUsageBar = callWithCommon(UI.createUsageBar);
|
||||||
funcs.updateTags = callWithCommon(UI.updateTags);
|
funcs.updateTags = callWithCommon(UI.updateTags);
|
||||||
funcs.displayThumbnail = UI.displayThumbnail;
|
|
||||||
|
// Thumb
|
||||||
|
funcs.displayThumbnail = Thumb.displayThumbnail;
|
||||||
|
|
||||||
// History
|
// History
|
||||||
funcs.getHistory = callWithCommon(History.create);
|
funcs.getHistory = callWithCommon(History.create);
|
||||||
|
|||||||
@@ -168,4 +168,8 @@ define({
|
|||||||
// in the drive at registration.
|
// in the drive at registration.
|
||||||
'Q_MERGE_ANON_DRIVE': true,
|
'Q_MERGE_ANON_DRIVE': true,
|
||||||
|
|
||||||
|
// Add or remove the avatar from the profile.
|
||||||
|
// We have to pin/unpin the avatar and store/remove the value from the user object
|
||||||
|
'Q_PROFILE_AVATAR_ADD': true,
|
||||||
|
'Q_PROFILE_AVATAR_REMOVE': true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ define([
|
|||||||
var $userlistContent = toolbar.userlistContent;
|
var $userlistContent = toolbar.userlistContent;
|
||||||
|
|
||||||
var metadataMgr = config.metadataMgr;
|
var metadataMgr = config.metadataMgr;
|
||||||
|
var online = metadataMgr.isConnected();
|
||||||
var userData = metadataMgr.getMetadata().users;
|
var userData = metadataMgr.getMetadata().users;
|
||||||
var viewers = metadataMgr.getViewers();
|
var viewers = metadataMgr.getViewers();
|
||||||
var priv = metadataMgr.getPrivateData();
|
var priv = metadataMgr.getPrivateData();
|
||||||
@@ -184,7 +185,23 @@ define([
|
|||||||
// Update the userlist
|
// Update the userlist
|
||||||
var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html('');
|
var $editUsers = $userlistContent.find('.' + USERLIST_CLS).html('');
|
||||||
|
|
||||||
var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'});
|
var $editUsersList = $('<div>', {'class': 'cp-toolbar-userlist-others'})
|
||||||
|
.appendTo($editUsers);
|
||||||
|
|
||||||
|
if (!online) {
|
||||||
|
$('<em>').text(Messages.userlist_offline).appendTo($editUsersList);
|
||||||
|
numberOfEditUsers = '?';
|
||||||
|
numberOfViewUsers = '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the buttons
|
||||||
|
var fa_editusers = '<span class="fa fa-users"></span>';
|
||||||
|
var fa_viewusers = '<span class="fa fa-eye"></span>';
|
||||||
|
var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + ' ' + fa_viewusers + ' ' + numberOfViewUsers);
|
||||||
|
$userButtons.find('.cp-dropdown-button-title').html('').append($spansmall);
|
||||||
|
|
||||||
|
if (!online) { return; }
|
||||||
|
// Display the userlist
|
||||||
|
|
||||||
// Editors
|
// Editors
|
||||||
var pendingFriends = Common.getPendingFriends();
|
var pendingFriends = Common.getPendingFriends();
|
||||||
@@ -237,7 +254,6 @@ define([
|
|||||||
$span.data('uid', data.uid);
|
$span.data('uid', data.uid);
|
||||||
$editUsersList.append($span);
|
$editUsersList.append($span);
|
||||||
});
|
});
|
||||||
$editUsers.append($editUsersList);
|
|
||||||
|
|
||||||
// Viewers
|
// Viewers
|
||||||
if (numberOfViewUsers > 0) {
|
if (numberOfViewUsers > 0) {
|
||||||
@@ -246,12 +262,6 @@ define([
|
|||||||
viewText += numberOfViewUsers + ' ' + viewerText + '</div>';
|
viewText += numberOfViewUsers + ' ' + viewerText + '</div>';
|
||||||
$editUsers.append(viewText);
|
$editUsers.append(viewText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the buttons
|
|
||||||
var fa_editusers = '<span class="fa fa-users"></span>';
|
|
||||||
var fa_viewusers = '<span class="fa fa-eye"></span>';
|
|
||||||
var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + ' ' + fa_viewusers + ' ' + numberOfViewUsers);
|
|
||||||
$userButtons.find('.cp-dropdown-button-title').html('').append($spansmall);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var initUserList = function (toolbar, config) {
|
var initUserList = function (toolbar, config) {
|
||||||
@@ -691,6 +701,7 @@ define([
|
|||||||
var typing = -1;
|
var typing = -1;
|
||||||
var kickSpinner = function (toolbar, config/*, local*/) {
|
var kickSpinner = function (toolbar, config/*, local*/) {
|
||||||
if (!toolbar.spinner) { return; }
|
if (!toolbar.spinner) { return; }
|
||||||
|
if (toolbar.isErrorState) { return; }
|
||||||
var $spin = toolbar.spinner;
|
var $spin = toolbar.spinner;
|
||||||
|
|
||||||
if (typing === -1) {
|
if (typing === -1) {
|
||||||
@@ -886,6 +897,7 @@ define([
|
|||||||
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
|
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
|
||||||
if (typeof name === "undefined") { return; }
|
if (typeof name === "undefined") { return; }
|
||||||
name = name || Messages.anonymous;
|
name = name || Messages.anonymous;
|
||||||
|
if (Config.disableUserlistNotifications) { return; }
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case 1:
|
case 1:
|
||||||
Cryptpad.log(Messages._getKey("notifyJoined", [name]));
|
Cryptpad.log(Messages._getKey("notifyJoined", [name]));
|
||||||
@@ -920,6 +932,7 @@ define([
|
|||||||
return count;
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var joined = false;
|
||||||
metadataMgr.onChange(function () {
|
metadataMgr.onChange(function () {
|
||||||
var newdata = metadataMgr.getMetadata().users;
|
var newdata = metadataMgr.getMetadata().users;
|
||||||
var netfluxIds = Object.keys(newdata);
|
var netfluxIds = Object.keys(newdata);
|
||||||
@@ -948,7 +961,7 @@ define([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (var k in newdata) {
|
for (var k in newdata) {
|
||||||
if (k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) {
|
if (joined && k !== userNetfluxId && netfluxIds.indexOf(k) !== -1) {
|
||||||
if (typeof oldUserData[k] === "undefined") {
|
if (typeof oldUserData[k] === "undefined") {
|
||||||
// if the same uid is already present in the userdata, don't notify
|
// if the same uid is already present in the userdata, don't notify
|
||||||
if (!userPresent(k, newdata[k], oldUserData)) {
|
if (!userPresent(k, newdata[k], oldUserData)) {
|
||||||
@@ -959,6 +972,7 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
joined = true;
|
||||||
oldUserData = JSON.parse(JSON.stringify(newdata));
|
oldUserData = JSON.parse(JSON.stringify(newdata));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1047,6 +1061,17 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toolbar.errorState = function (state, error) {
|
||||||
|
toolbar.isErrorState = state;
|
||||||
|
if (toolbar.spinner) {
|
||||||
|
if (!state) {
|
||||||
|
return void kickSpinner(toolbar, config);
|
||||||
|
}
|
||||||
|
var txt = Messages._getKey('errorState', [error]);
|
||||||
|
toolbar.spinner.text(txt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// When the pad is moved to the trash (forget button)
|
// When the pad is moved to the trash (forget button)
|
||||||
toolbar.forgotten = function (/*userId*/) {
|
toolbar.forgotten = function (/*userId*/) {
|
||||||
toolbar.connected = false;
|
toolbar.connected = false;
|
||||||
|
|||||||
@@ -3,10 +3,9 @@ define([
|
|||||||
'/common/cryptpad-common.js',
|
'/common/cryptpad-common.js',
|
||||||
'/common/hyperscript.js',
|
'/common/hyperscript.js',
|
||||||
'/bower_components/marked/marked.min.js',
|
'/bower_components/marked/marked.min.js',
|
||||||
], function ($, Cryptpad, h, Marked) {
|
'/common/media-tag.js',
|
||||||
|
], function ($, Cryptpad, h, Marked, MediaTag) {
|
||||||
'use strict';
|
'use strict';
|
||||||
// TODO use our fancy markdown and support media-tags
|
|
||||||
Marked.setOptions({ sanitize: true, });
|
|
||||||
|
|
||||||
var UI = {};
|
var UI = {};
|
||||||
var Messages = Cryptpad.Messages;
|
var Messages = Cryptpad.Messages;
|
||||||
@@ -15,6 +14,12 @@ define([
|
|||||||
var d = h('div.cp-app-contacts-content');
|
var d = h('div.cp-app-contacts-content');
|
||||||
try {
|
try {
|
||||||
d.innerHTML = Marked(md || '');
|
d.innerHTML = Marked(md || '');
|
||||||
|
var $d = $(d);
|
||||||
|
// remove potentially malicious elements
|
||||||
|
$d.find('script, iframe, object, applet, video, audio').remove();
|
||||||
|
|
||||||
|
// activate media-tags
|
||||||
|
$d.find('media-tag').each(function (i, e) { MediaTag(e); });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(md);
|
console.error(md);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|||||||
@@ -58,13 +58,15 @@ min-height: auto;
|
|||||||
}
|
}
|
||||||
.cp-app-drive-element-name {
|
.cp-app-drive-element-name {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 48px;
|
height: 24px;
|
||||||
margin: 8px 0;
|
margin: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
//align-items: center;
|
//align-items: center;
|
||||||
//justify-content: center;
|
//justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
//text-overflow: ellipsis;
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
.cp-app-drive-element-truncated {
|
.cp-app-drive-element-truncated {
|
||||||
@@ -83,8 +85,8 @@ min-height: auto;
|
|||||||
.fa {
|
.fa {
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
font-size: 48px;
|
font-size: 64px;
|
||||||
margin: 8px 0;
|
margin: 18px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
&.listonly {
|
&.listonly {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -518,10 +520,15 @@ span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.cp-app-drive-element-thumbnail {
|
.cp-app-drive-element-thumbnail {
|
||||||
max-width: 64px;
|
max-width: 100px;
|
||||||
max-height: 64px;
|
max-height: 100px;
|
||||||
& ~ .fa {
|
& ~ .fa {
|
||||||
display: none;
|
display: inline;
|
||||||
|
font-size: 17px;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
left: 3px;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1145,6 +1145,10 @@ define([
|
|||||||
if (!data) { return void logError("No data for the file", element); }
|
if (!data) { return void logError("No data for the file", element); }
|
||||||
|
|
||||||
var hrefData = Cryptpad.parsePadUrl(data.href);
|
var hrefData = Cryptpad.parsePadUrl(data.href);
|
||||||
|
if (hrefData.type) {
|
||||||
|
$span.addClass('cp-border-color-'+hrefData.type);
|
||||||
|
}
|
||||||
|
|
||||||
var $state = $('<span>', {'class': 'cp-app-drive-element-state'});
|
var $state = $('<span>', {'class': 'cp-app-drive-element-state'});
|
||||||
if (hrefData.hashData && hrefData.hashData.mode === 'view') {
|
if (hrefData.hashData && hrefData.hashData.mode === 'view') {
|
||||||
var $ro = $readonlyIcon.clone().appendTo($state);
|
var $ro = $readonlyIcon.clone().appendTo($state);
|
||||||
@@ -1161,6 +1165,7 @@ define([
|
|||||||
var $name = $('<span>', {'class': 'cp-app-drive-element-name'}).text(name);
|
var $name = $('<span>', {'class': 'cp-app-drive-element-name'}).text(name);
|
||||||
$span.append($name);
|
$span.append($name);
|
||||||
$span.append($state);
|
$span.append($state);
|
||||||
|
$span.attr('title', name);
|
||||||
|
|
||||||
var type = Messages.type[hrefData.type] || hrefData.type;
|
var type = Messages.type[hrefData.type] || hrefData.type;
|
||||||
common.displayThumbnail(data.href, $span, function ($thumb) {
|
common.displayThumbnail(data.href, $span, function ($thumb) {
|
||||||
@@ -1199,6 +1204,7 @@ define([
|
|||||||
var $files = $('<span>', {
|
var $files = $('<span>', {
|
||||||
'class': 'cp-app-drive-element-files cp-app-drive-element-list'
|
'class': 'cp-app-drive-element-files cp-app-drive-element-list'
|
||||||
}).text(files);
|
}).text(files);
|
||||||
|
$span.attr('title', key);
|
||||||
$span.append($name).append($state).append($subfolders).append($files);
|
$span.append($name).append($state).append($subfolders).append($files);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1512,7 +1518,7 @@ define([
|
|||||||
if (isInRoot) {
|
if (isInRoot) {
|
||||||
options.push({
|
options.push({
|
||||||
tag: 'a',
|
tag: 'a',
|
||||||
attributes: {'class': 'cp-app-drive-new-older'},
|
attributes: {'class': 'cp-app-drive-new-folder'},
|
||||||
content: $('<div>').append($folderIcon.clone()).html() + Messages.fm_folder
|
content: $('<div>').append($folderIcon.clone()).html() + Messages.fm_folder
|
||||||
});
|
});
|
||||||
options.push({tag: 'hr'});
|
options.push({tag: 'hr'});
|
||||||
@@ -2197,7 +2203,7 @@ define([
|
|||||||
}
|
}
|
||||||
$content.append($info).append($dirContent);
|
$content.append($info).append($dirContent);
|
||||||
|
|
||||||
var $truncated = $('<span>', {'class': 'cp-app-drive-element-truncated'}).text('...');
|
/*var $truncated = $('<span>', {'class': 'cp-app-drive-element-truncated'}).text('...');
|
||||||
$content.find('.cp-app-drive-element').each(function (idx, el) {
|
$content.find('.cp-app-drive-element').each(function (idx, el) {
|
||||||
var $name = $(el).find('.cp-app-drive-element-name');
|
var $name = $(el).find('.cp-app-drive-element-name');
|
||||||
if ($name.length === 0) { return; }
|
if ($name.length === 0) { return; }
|
||||||
@@ -2206,7 +2212,7 @@ define([
|
|||||||
$tr.attr('title', $name.text());
|
$tr.attr('title', $name.text());
|
||||||
$(el).append($tr);
|
$(el).append($tr);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
|
|
||||||
$content.scrollTop(s);
|
$content.scrollTop(s);
|
||||||
appStatus.ready(true);
|
appStatus.ready(true);
|
||||||
|
|||||||
@@ -47,6 +47,11 @@
|
|||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100px;
|
||||||
|
max-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
.cp-filepicker-content-element-name {
|
.cp-filepicker-content-element-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
20
www/oldprofile/index.html
Normal file
20
www/oldprofile/index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="cp">
|
||||||
|
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
||||||
|
<head>
|
||||||
|
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
|
||||||
|
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css">
|
||||||
|
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" />
|
||||||
|
</head>
|
||||||
|
<body class="html">
|
||||||
|
<noscript>
|
||||||
|
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
||||||
|
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
||||||
|
</noscript>
|
||||||
|
</html>
|
||||||
532
www/oldprofile/main.js
Normal file
532
www/oldprofile/main.js
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
require.config({
|
||||||
|
paths: {
|
||||||
|
cm: '/bower_components/codemirror'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/bower_components/marked/marked.min.js',
|
||||||
|
'/common/toolbar2.js',
|
||||||
|
'cm/lib/codemirror',
|
||||||
|
'cm/mode/markdown/markdown',
|
||||||
|
'less!/profile/main.less',
|
||||||
|
'less!/customize/src/less/toolbar.less',
|
||||||
|
'less!/customize/src/less/cryptpad.less',
|
||||||
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
|
], function ($, Cryptpad, Listmap, Crypto, Marked, Toolbar, CodeMirror) {
|
||||||
|
|
||||||
|
var APP = window.APP = {
|
||||||
|
Cryptpad: Cryptpad,
|
||||||
|
_onRefresh: []
|
||||||
|
};
|
||||||
|
|
||||||
|
$(window.document).on('decryption', function (e) {
|
||||||
|
var decrypted = e.originalEvent;
|
||||||
|
if (decrypted.callback) { decrypted.callback(); }
|
||||||
|
})
|
||||||
|
.on('decryptionError', function (e) {
|
||||||
|
var error = e.originalEvent;
|
||||||
|
Cryptpad.alert(error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Marked
|
||||||
|
var renderer = new Marked.Renderer();
|
||||||
|
Marked.setOptions({
|
||||||
|
renderer: renderer,
|
||||||
|
sanitize: true
|
||||||
|
});
|
||||||
|
// Tasks list
|
||||||
|
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
||||||
|
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
||||||
|
renderer.listitem = function (text) {
|
||||||
|
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
|
||||||
|
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
|
||||||
|
if (isCheckedTaskItem) {
|
||||||
|
text = text.replace(checkedTaskItemPtn,
|
||||||
|
'<i class="fa fa-check-square" aria-hidden="true"></i> ') + '\n';
|
||||||
|
}
|
||||||
|
if (isUncheckedTaskItem) {
|
||||||
|
text = text.replace(uncheckedTaskItemPtn,
|
||||||
|
'<i class="fa fa-square-o" aria-hidden="true"></i> ') + '\n';
|
||||||
|
}
|
||||||
|
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
|
||||||
|
return '<li'+ cls + '>' + text + '</li>\n';
|
||||||
|
};
|
||||||
|
/*renderer.image = function (href, title, text) {
|
||||||
|
if (href.slice(0,6) === '/file/') {
|
||||||
|
var parsed = Cryptpad.parsePadUrl(href);
|
||||||
|
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
||||||
|
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||||
|
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
|
||||||
|
mt += '</media-tag>';
|
||||||
|
return mt;
|
||||||
|
}
|
||||||
|
var out = '<img src="' + href + '" alt="' + text + '"';
|
||||||
|
if (title) {
|
||||||
|
out += ' title="' + title + '"';
|
||||||
|
}
|
||||||
|
out += this.options.xhtml ? '/>' : '>';
|
||||||
|
return out;
|
||||||
|
};*/
|
||||||
|
|
||||||
|
var Messages = Cryptpad.Messages;
|
||||||
|
|
||||||
|
var DISPLAYNAME_ID = "displayName";
|
||||||
|
var LINK_ID = "link";
|
||||||
|
var AVATAR_ID = "avatar";
|
||||||
|
var DESCRIPTION_ID = "description";
|
||||||
|
var PUBKEY_ID = "pubKey";
|
||||||
|
var CREATE_ID = "createProfile";
|
||||||
|
var HEADER_ID = "header";
|
||||||
|
var HEADER_RIGHT_ID = "rightside";
|
||||||
|
var CREATE_INVITE_BUTTON = 'inviteButton'; /* jshint ignore: line */
|
||||||
|
var VIEW_PROFILE_BUTTON = 'viewProfileButton';
|
||||||
|
|
||||||
|
var createEditableInput = function ($block, name, ph, getValue, setValue, realtime, fallbackValue) {
|
||||||
|
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
|
||||||
|
var lastVal;
|
||||||
|
getValue(function (value) {
|
||||||
|
lastVal = value;
|
||||||
|
var $input = $('<input>', {
|
||||||
|
'id': name+'Input',
|
||||||
|
placeholder: ph
|
||||||
|
}).val(value);
|
||||||
|
var $icon = $('<span>', {'class': 'fa fa-pencil edit'});
|
||||||
|
var editing = false;
|
||||||
|
var todo = function () {
|
||||||
|
if (editing) { return; }
|
||||||
|
editing = true;
|
||||||
|
|
||||||
|
var newVal = $input.val().trim();
|
||||||
|
|
||||||
|
if (newVal === lastVal) {
|
||||||
|
editing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(newVal, function (err) {
|
||||||
|
if (err) { return void console.error(err); }
|
||||||
|
Cryptpad.whenRealtimeSyncs(realtime, function () {
|
||||||
|
lastVal = newVal;
|
||||||
|
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
|
||||||
|
editing = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$input.on('keyup', function (e) {
|
||||||
|
if (e.which === 13) { return void todo(); }
|
||||||
|
if (e.which === 27) {
|
||||||
|
$input.val(lastVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$icon.click(function () { $input.focus(); });
|
||||||
|
$input.focus(function () {
|
||||||
|
$input.width('');
|
||||||
|
});
|
||||||
|
$input.focusout(todo);
|
||||||
|
$block.append($input).append($icon);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* jshint ignore:start */
|
||||||
|
var isFriend = function (proxy, edKey) {
|
||||||
|
var friends = Cryptpad.find(proxy, ['friends']);
|
||||||
|
return typeof(edKey) === 'string' && friends && (edKey in friends);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addCreateInviteLinkButton = function ($container) {
|
||||||
|
return;
|
||||||
|
var obj = APP.lm.proxy;
|
||||||
|
|
||||||
|
var proxy = Cryptpad.getProxy();
|
||||||
|
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
|
||||||
|
|
||||||
|
var edKey = obj.edKey;
|
||||||
|
var curveKey = obj.curveKey;
|
||||||
|
|
||||||
|
if (!APP.readOnly || !curveKey || !edKey || userViewHash === window.location.hash.slice(1) || isFriend(proxy, edKey)) {
|
||||||
|
//console.log("edit mode or missing curve key, or you're viewing your own profile");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize user inputs
|
||||||
|
|
||||||
|
var unsafeName = obj.name || '';
|
||||||
|
console.log(unsafeName);
|
||||||
|
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
|
||||||
|
console.log(name);
|
||||||
|
|
||||||
|
console.log("Creating invite button");
|
||||||
|
$("<button>", {
|
||||||
|
id: CREATE_INVITE_BUTTON,
|
||||||
|
title: Messages.profile_inviteButtonTitle,
|
||||||
|
})
|
||||||
|
.addClass('btn btn-success')
|
||||||
|
.text(Messages.profile_inviteButton)
|
||||||
|
.click(function () {
|
||||||
|
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
|
||||||
|
if (!yes) { return; }
|
||||||
|
console.log(obj.curveKey);
|
||||||
|
Cryptpad.alert("TODO");
|
||||||
|
// TODO create a listmap object using your curve keys
|
||||||
|
// TODO fill the listmap object with your invite data
|
||||||
|
// TODO generate link to invite object
|
||||||
|
// TODO copy invite link to clipboard
|
||||||
|
}, null, true);
|
||||||
|
})
|
||||||
|
.appendTo($container);
|
||||||
|
};
|
||||||
|
/* jshint ignore:end */
|
||||||
|
|
||||||
|
var addViewButton = function ($container) {
|
||||||
|
if (!Cryptpad.isLoggedIn() || window.location.hash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = Cryptpad.find(Cryptpad.getProxy(), ['profile', 'view']);
|
||||||
|
var url = '/profile/#' + hash;
|
||||||
|
|
||||||
|
var $button = $('<button>', {
|
||||||
|
'class': 'btn btn-success',
|
||||||
|
id: VIEW_PROFILE_BUTTON,
|
||||||
|
})
|
||||||
|
.text(Messages.profile_viewMyProfile)
|
||||||
|
.click(function () {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
});
|
||||||
|
$container.append($button);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addDisplayName = function ($container) {
|
||||||
|
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
|
||||||
|
|
||||||
|
|
||||||
|
var getValue = function (cb) {
|
||||||
|
cb(APP.lm.proxy.name);
|
||||||
|
};
|
||||||
|
var placeholder = Messages.profile_namePlaceholder;
|
||||||
|
if (APP.readOnly) {
|
||||||
|
var $span = $('<span>', {'class': DISPLAYNAME_ID}).appendTo($block);
|
||||||
|
getValue(function (value) {
|
||||||
|
$span.text(value || Messages.anonymous);
|
||||||
|
});
|
||||||
|
|
||||||
|
//addCreateInviteLinkButton($block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var setValue = function (value, cb) {
|
||||||
|
APP.lm.proxy.name = value;
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
var rt = Cryptpad.getStore().getProxy().info.realtime;
|
||||||
|
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, rt, Messages.anonymous);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addLink = function ($container) {
|
||||||
|
var $block = $('<div>', {id: LINK_ID}).appendTo($container);
|
||||||
|
var getValue = function (cb) {
|
||||||
|
cb(APP.lm.proxy.url);
|
||||||
|
};
|
||||||
|
if (APP.readOnly) {
|
||||||
|
var $a = $('<a>', {
|
||||||
|
'class': LINK_ID,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noreferrer noopener'
|
||||||
|
}).appendTo($block);
|
||||||
|
getValue(function (value) {
|
||||||
|
if (!value) {
|
||||||
|
return void $a.hide();
|
||||||
|
}
|
||||||
|
$a.attr('href', value).text(value);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var setValue = function (value, cb) {
|
||||||
|
APP.lm.proxy.url = value;
|
||||||
|
cb();
|
||||||
|
};
|
||||||
|
var rt = APP.lm.realtime;
|
||||||
|
var placeholder = Messages.profile_urlPlaceholder;
|
||||||
|
createEditableInput($block, LINK_ID, placeholder, getValue, setValue, rt);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addAvatar = function ($container) {
|
||||||
|
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
|
||||||
|
var $span = $('<span>').appendTo($block);
|
||||||
|
var allowedMediaTypes = Cryptpad.avatarAllowedTypes;
|
||||||
|
var displayAvatar = function () {
|
||||||
|
$span.html('');
|
||||||
|
if (!APP.lm.proxy.avatar) {
|
||||||
|
$('<img>', {
|
||||||
|
src: '/customize/images/avatar.png',
|
||||||
|
title: Messages.profile_avatar,
|
||||||
|
alt: 'Avatar'
|
||||||
|
}).appendTo($span);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Cryptpad.displayAvatar($span, APP.lm.proxy.avatar);
|
||||||
|
|
||||||
|
if (APP.readOnly) { return; }
|
||||||
|
|
||||||
|
var $delButton = $('<button>', {
|
||||||
|
'class': 'delete btn btn-danger fa fa-times',
|
||||||
|
title: Messages.fc_delete
|
||||||
|
});
|
||||||
|
$span.append($delButton);
|
||||||
|
$delButton.click(function () {
|
||||||
|
var oldChanId = Cryptpad.hrefToHexChannelId(APP.lm.proxy.avatar);
|
||||||
|
Cryptpad.unpinPads([oldChanId], function (e) {
|
||||||
|
if (e) { Cryptpad.log(e); }
|
||||||
|
delete APP.lm.proxy.avatar;
|
||||||
|
delete Cryptpad.getProxy().profile.avatar;
|
||||||
|
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
||||||
|
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
|
||||||
|
Cryptpad.whenRealtimeSyncs(driveRt, function () {
|
||||||
|
displayAvatar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
displayAvatar();
|
||||||
|
if (APP.readOnly) { return; }
|
||||||
|
|
||||||
|
var fmConfig = {
|
||||||
|
noHandlers: true,
|
||||||
|
noStore: true,
|
||||||
|
body: $('body'),
|
||||||
|
onUploaded: function (ev, data) {
|
||||||
|
var chanId = Cryptpad.hrefToHexChannelId(data.url);
|
||||||
|
var profile = Cryptpad.getProxy().profile;
|
||||||
|
var old = profile.avatar;
|
||||||
|
var todo = function () {
|
||||||
|
Cryptpad.pinPads([chanId], function (e) {
|
||||||
|
if (e) { return void Cryptpad.log(e); }
|
||||||
|
APP.lm.proxy.avatar = data.url;
|
||||||
|
Cryptpad.getProxy().profile.avatar = data.url;
|
||||||
|
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
||||||
|
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
|
||||||
|
Cryptpad.whenRealtimeSyncs(driveRt, function () {
|
||||||
|
displayAvatar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (old) {
|
||||||
|
var oldChanId = Cryptpad.hrefToHexChannelId(old);
|
||||||
|
Cryptpad.unpinPads([oldChanId], function (e) {
|
||||||
|
if (e) { Cryptpad.log(e); }
|
||||||
|
todo();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
todo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APP.FM = Cryptpad.createFileManager(fmConfig);
|
||||||
|
var data = {
|
||||||
|
FM: APP.FM,
|
||||||
|
filter: function (file) {
|
||||||
|
var sizeMB = Cryptpad.bytesToMegabytes(file.size);
|
||||||
|
var type = file.type;
|
||||||
|
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1;
|
||||||
|
},
|
||||||
|
accept: ".gif,.jpg,.jpeg,.png"
|
||||||
|
};
|
||||||
|
var $upButton = Cryptpad.createButton('upload', false, data);
|
||||||
|
$upButton.text(Messages.profile_upload);
|
||||||
|
$upButton.prepend($('<span>', {'class': 'fa fa-upload'}));
|
||||||
|
$block.append($upButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addDescription = function ($container) {
|
||||||
|
var $block = $('<div>', {id: DESCRIPTION_ID}).appendTo($container);
|
||||||
|
|
||||||
|
if (APP.readOnly) {
|
||||||
|
if (!(APP.lm.proxy.description || "").trim()) { return void $block.hide(); }
|
||||||
|
var $div = $('<div>', {'class': 'rendered'}).appendTo($block);
|
||||||
|
var val = Marked(APP.lm.proxy.description);
|
||||||
|
$div.html(val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('<h3>').text(Messages.profile_description).insertBefore($block);
|
||||||
|
|
||||||
|
var $ok = $('<span>', {'class': 'ok fa fa-check', title: Messages.saved}).appendTo($block);
|
||||||
|
var $spinner = $('<span>', {'class': 'spin fa fa-spinner fa-pulse'}).appendTo($block);
|
||||||
|
var $textarea = $('<textarea>').val(APP.lm.proxy.description || '');
|
||||||
|
$block.append($textarea);
|
||||||
|
var editor = APP.editor = CodeMirror.fromTextArea($textarea[0], {
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
styleActiveLine : true,
|
||||||
|
mode: "markdown",
|
||||||
|
});
|
||||||
|
|
||||||
|
var onLocal = function () {
|
||||||
|
$ok.hide();
|
||||||
|
$spinner.show();
|
||||||
|
var val = editor.getValue();
|
||||||
|
APP.lm.proxy.description = val;
|
||||||
|
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
||||||
|
$ok.show();
|
||||||
|
$spinner.hide();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.on('change', onLocal);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addPublicKey = function ($container) {
|
||||||
|
var $block = $('<div>', {id: PUBKEY_ID});
|
||||||
|
$container.append($block);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createLeftside = function () {
|
||||||
|
var $categories = $('<div>', {'class': 'categories'}).appendTo(APP.$leftside);
|
||||||
|
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
|
||||||
|
|
||||||
|
var $category = $('<div>', {'class': 'category'}).appendTo($categories);
|
||||||
|
$category.append($('<span>', {'class': 'fa fa-user'}));
|
||||||
|
$category.addClass('active');
|
||||||
|
$category.append(Messages.profileButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createToolbar = function () {
|
||||||
|
var displayed = ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'];
|
||||||
|
var configTb = {
|
||||||
|
displayed: displayed,
|
||||||
|
ifrw: window,
|
||||||
|
common: Cryptpad,
|
||||||
|
$container: APP.$toolbar,
|
||||||
|
pageTitle: Messages.profileButton
|
||||||
|
};
|
||||||
|
var toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||||
|
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
|
||||||
|
};
|
||||||
|
|
||||||
|
var onReady = function () {
|
||||||
|
APP.$container.find('#'+CREATE_ID).remove();
|
||||||
|
|
||||||
|
var obj = APP.lm && APP.lm.proxy;
|
||||||
|
if (!APP.readOnly) {
|
||||||
|
var pubKeys = Cryptpad.getPublicKeys();
|
||||||
|
if (pubKeys && pubKeys.curve) {
|
||||||
|
obj.curveKey = pubKeys.curve;
|
||||||
|
obj.edKey = pubKeys.ed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!APP.initialized) {
|
||||||
|
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
|
||||||
|
addAvatar($header);
|
||||||
|
var $rightside = $('<div>', {id: HEADER_RIGHT_ID}).appendTo($header);
|
||||||
|
addDisplayName($rightside);
|
||||||
|
addLink($rightside);
|
||||||
|
addDescription(APP.$rightside);
|
||||||
|
addViewButton(APP.$rightside); //$rightside);
|
||||||
|
addPublicKey(APP.$rightside);
|
||||||
|
APP.initialized = true;
|
||||||
|
createLeftside();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onInit = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
var onDisconnect = function () {};
|
||||||
|
var onChange = function () {};
|
||||||
|
|
||||||
|
var andThen = function (profileHash) {
|
||||||
|
var secret = Cryptpad.getSecrets('profile', profileHash);
|
||||||
|
var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||||
|
var listmapConfig = {
|
||||||
|
data: {},
|
||||||
|
websocketURL: Cryptpad.getWebsocketURL(),
|
||||||
|
channel: secret.channel,
|
||||||
|
readOnly: readOnly,
|
||||||
|
validateKey: secret.keys.validateKey || undefined,
|
||||||
|
crypto: Crypto.createEncryptor(secret.keys),
|
||||||
|
userName: 'profile',
|
||||||
|
logLevel: 1,
|
||||||
|
};
|
||||||
|
var lm = APP.lm = Listmap.create(listmapConfig);
|
||||||
|
lm.proxy.on('create', onInit)
|
||||||
|
.on('ready', onReady)
|
||||||
|
.on('disconnect', onDisconnect)
|
||||||
|
.on('change', [], onChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
var getOrCreateProfile = function () {
|
||||||
|
var obj = Cryptpad.getStore().getProxy().proxy;
|
||||||
|
if (obj.profile && obj.profile.view && obj.profile.edit) {
|
||||||
|
return void andThen(obj.profile.edit);
|
||||||
|
}
|
||||||
|
// If the user doesn't have a public profile, ask them if they want to create one
|
||||||
|
var todo = function () {
|
||||||
|
var secret = Cryptpad.getSecrets();
|
||||||
|
obj.profile = {};
|
||||||
|
var channel = Cryptpad.createChannelId();
|
||||||
|
Cryptpad.pinPads([channel], function (e) {
|
||||||
|
if (e) {
|
||||||
|
if (e === 'E_OVER_LIMIT') {
|
||||||
|
Cryptpad.alert(Messages.pinLimitNotPinned, null, true);
|
||||||
|
}
|
||||||
|
return void Cryptpad.log(Messages._getKey('profile_error', [e]));
|
||||||
|
}
|
||||||
|
obj.profile.edit = Cryptpad.getEditHashFromKeys(channel, secret.keys);
|
||||||
|
obj.profile.view = Cryptpad.getViewHashFromKeys(channel, secret.keys);
|
||||||
|
andThen(obj.profile.edit);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
|
||||||
|
if (!Cryptpad.isLoggedIn()) {
|
||||||
|
var $p = $('<p>', {id: CREATE_ID}).append(Messages.profile_register);
|
||||||
|
var $a = $('<a>', {
|
||||||
|
href: '/register/'
|
||||||
|
});
|
||||||
|
$('<button>', {
|
||||||
|
'class': 'btn btn-success',
|
||||||
|
}).text(Messages.login_register).appendTo($a);
|
||||||
|
$p.append($('<br>')).append($a);
|
||||||
|
APP.$rightside.append($p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make an empty profile for the user on their first visit
|
||||||
|
todo();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onCryptpadReady = function () {
|
||||||
|
APP.$leftside = $('<div>', {id: 'leftSide'}).appendTo(APP.$container);
|
||||||
|
APP.$rightside = $('<div>', {id: 'rightSide'}).appendTo(APP.$container);
|
||||||
|
|
||||||
|
createToolbar();
|
||||||
|
|
||||||
|
if (window.location.hash) {
|
||||||
|
return void andThen(window.location.hash.slice(1));
|
||||||
|
}
|
||||||
|
getOrCreateProfile();
|
||||||
|
};
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
$(window).click(function () {
|
||||||
|
$('.cp-dropdown-content').hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
APP.$container = $('#container');
|
||||||
|
APP.$toolbar = $('#toolbar');
|
||||||
|
|
||||||
|
Cryptpad.ready(function () {
|
||||||
|
Cryptpad.reportAppUsage();
|
||||||
|
onCryptpadReady();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
378
www/oldtodo/example/assets/todomvc-app-css/index.css
Normal file
378
www/oldtodo/example/assets/todomvc-app-css/index.css
Normal file
@@ -0,0 +1,378 @@
|
|||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
font-size: 100%;
|
||||||
|
vertical-align: baseline;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-font-smoothing: antialiased;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.4em;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #4d4d4d;
|
||||||
|
min-width: 230px;
|
||||||
|
max-width: 550px;
|
||||||
|
margin: 0 auto;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-font-smoothing: antialiased;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input[type="checkbox"] {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp {
|
||||||
|
background: #fff;
|
||||||
|
margin: 130px 0 40px 0;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
|
||||||
|
0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::-webkit-input-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::-moz-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp input::input-placeholder {
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todoapp h1 {
|
||||||
|
position: absolute;
|
||||||
|
top: -155px;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 100px;
|
||||||
|
font-weight: 100;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(175, 47, 47, 0.15);
|
||||||
|
-webkit-text-rendering: optimizeLegibility;
|
||||||
|
-moz-text-rendering: optimizeLegibility;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo,
|
||||||
|
.edit {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
line-height: 1.4em;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-font-smoothing: antialiased;
|
||||||
|
font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-todo {
|
||||||
|
padding: 16px 16px 16px 60px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(0, 0, 0, 0.003);
|
||||||
|
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
border-top: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
label[for='toggle-all'] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all {
|
||||||
|
position: absolute;
|
||||||
|
top: -55px;
|
||||||
|
left: -12px;
|
||||||
|
width: 60px;
|
||||||
|
height: 34px;
|
||||||
|
text-align: center;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all:before {
|
||||||
|
content: '❯';
|
||||||
|
font-size: 22px;
|
||||||
|
color: #e6e6e6;
|
||||||
|
padding: 10px 27px 10px 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all:checked:before {
|
||||||
|
color: #737373;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li {
|
||||||
|
position: relative;
|
||||||
|
font-size: 24px;
|
||||||
|
border-bottom: 1px solid #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing {
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing .edit {
|
||||||
|
display: block;
|
||||||
|
width: 506px;
|
||||||
|
padding: 13px 17px 12px 17px;
|
||||||
|
margin: 0 0 0 43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing .view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
text-align: center;
|
||||||
|
width: 40px;
|
||||||
|
/* auto, since non-WebKit browsers doesn't support input styling */
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
border: none; /* Mobile Safari */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle:after {
|
||||||
|
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle:checked:after {
|
||||||
|
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li label {
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-all;
|
||||||
|
padding: 15px 60px 15px 15px;
|
||||||
|
margin-left: 45px;
|
||||||
|
display: block;
|
||||||
|
line-height: 1.2;
|
||||||
|
transition: color 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.completed label {
|
||||||
|
color: #d9d9d9;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 10px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin: auto 0;
|
||||||
|
font-size: 30px;
|
||||||
|
color: #cc9a9a;
|
||||||
|
margin-bottom: 11px;
|
||||||
|
transition: color 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy:hover {
|
||||||
|
color: #af5b5e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .destroy:after {
|
||||||
|
content: '×';
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li:hover .destroy {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .edit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li.editing:last-child {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
color: #777;
|
||||||
|
padding: 10px 15px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid #e6e6e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
|
||||||
|
0 8px 0 -3px #f6f6f6,
|
||||||
|
0 9px 1px -3px rgba(0, 0, 0, 0.2),
|
||||||
|
0 16px 0 -6px #f6f6f6,
|
||||||
|
0 17px 2px -6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-count strong {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a {
|
||||||
|
color: inherit;
|
||||||
|
margin: 3px;
|
||||||
|
padding: 3px 7px;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a.selected,
|
||||||
|
.filters li a:hover {
|
||||||
|
border-color: rgba(175, 47, 47, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters li a.selected {
|
||||||
|
border-color: rgba(175, 47, 47, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed,
|
||||||
|
html .clear-completed:active {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
line-height: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-completed:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin: 65px auto 0;
|
||||||
|
color: #bfbfbf;
|
||||||
|
font-size: 10px;
|
||||||
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info p {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Hack to remove background from Mobile Safari.
|
||||||
|
Can't use it globally since it destroys checkboxes in Firefox
|
||||||
|
*/
|
||||||
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
|
.toggle-all,
|
||||||
|
.todo-list li .toggle {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list li .toggle {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-all {
|
||||||
|
-webkit-transform: rotate(90deg);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 430px) {
|
||||||
|
.footer {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
141
www/oldtodo/example/assets/todomvc-common/base.css
Normal file
141
www/oldtodo/example/assets/todomvc-common/base.css
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
hr {
|
||||||
|
margin: 20px 0;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px dashed #c5c5c5;
|
||||||
|
border-bottom: 1px dashed #f7f7f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn a {
|
||||||
|
font-weight: normal;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #b83f45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #787e7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn h3,
|
||||||
|
.learn h4,
|
||||||
|
.learn h5 {
|
||||||
|
margin: 10px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn h5 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 30px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn li {
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn p {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#issue-count {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote {
|
||||||
|
border: none;
|
||||||
|
margin: 20px 0 60px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote p {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote p:before {
|
||||||
|
content: '“';
|
||||||
|
font-size: 50px;
|
||||||
|
opacity: .15;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote p:after {
|
||||||
|
content: '”';
|
||||||
|
font-size: 50px;
|
||||||
|
opacity: .15;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -42px;
|
||||||
|
right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -40px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote footer img {
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote footer a {
|
||||||
|
margin-left: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speech-bubble {
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(0, 0, 0, .04);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speech-bubble:after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
right: 30px;
|
||||||
|
border: 13px solid transparent;
|
||||||
|
border-top-color: rgba(0, 0, 0, .04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn-bar > .learn {
|
||||||
|
position: absolute;
|
||||||
|
width: 272px;
|
||||||
|
top: 8px;
|
||||||
|
left: -300px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: rgba(255, 255, 255, .6);
|
||||||
|
transition-property: left;
|
||||||
|
transition-duration: 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 899px) {
|
||||||
|
.learn-bar {
|
||||||
|
width: auto;
|
||||||
|
padding-left: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.learn-bar > .learn {
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
249
www/oldtodo/example/assets/todomvc-common/base.js
Normal file
249
www/oldtodo/example/assets/todomvc-common/base.js
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
/* global _ */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* jshint ignore:start */
|
||||||
|
// Underscore's Template Module
|
||||||
|
// Courtesy of underscorejs.org
|
||||||
|
var _ = (function (_) {
|
||||||
|
_.defaults = function (object) {
|
||||||
|
if (!object) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
|
||||||
|
var iterable = arguments[argsIndex];
|
||||||
|
if (iterable) {
|
||||||
|
for (var key in iterable) {
|
||||||
|
if (object[key] == null) {
|
||||||
|
object[key] = iterable[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, Underscore uses ERB-style template delimiters, change the
|
||||||
|
// following template settings to use alternative delimiters.
|
||||||
|
_.templateSettings = {
|
||||||
|
evaluate : /<%([\s\S]+?)%>/g,
|
||||||
|
interpolate : /<%=([\s\S]+?)%>/g,
|
||||||
|
escape : /<%-([\s\S]+?)%>/g
|
||||||
|
};
|
||||||
|
|
||||||
|
// When customizing `templateSettings`, if you don't want to define an
|
||||||
|
// interpolation, evaluation or escaping regex, we need one that is
|
||||||
|
// guaranteed not to match.
|
||||||
|
var noMatch = /(.)^/;
|
||||||
|
|
||||||
|
// Certain characters need to be escaped so that they can be put into a
|
||||||
|
// string literal.
|
||||||
|
var escapes = {
|
||||||
|
"'": "'",
|
||||||
|
'\\': '\\',
|
||||||
|
'\r': 'r',
|
||||||
|
'\n': 'n',
|
||||||
|
'\t': 't',
|
||||||
|
'\u2028': 'u2028',
|
||||||
|
'\u2029': 'u2029'
|
||||||
|
};
|
||||||
|
|
||||||
|
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
|
||||||
|
|
||||||
|
// JavaScript micro-templating, similar to John Resig's implementation.
|
||||||
|
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
||||||
|
// and correctly escapes quotes within interpolated code.
|
||||||
|
_.template = function(text, data, settings) {
|
||||||
|
var render;
|
||||||
|
settings = _.defaults({}, settings, _.templateSettings);
|
||||||
|
|
||||||
|
// Combine delimiters into one regular expression via alternation.
|
||||||
|
var matcher = new RegExp([
|
||||||
|
(settings.escape || noMatch).source,
|
||||||
|
(settings.interpolate || noMatch).source,
|
||||||
|
(settings.evaluate || noMatch).source
|
||||||
|
].join('|') + '|$', 'g');
|
||||||
|
|
||||||
|
// Compile the template source, escaping string literals appropriately.
|
||||||
|
var index = 0;
|
||||||
|
var source = "__p+='";
|
||||||
|
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
|
||||||
|
source += text.slice(index, offset)
|
||||||
|
.replace(escaper, function(match) { return '\\' + escapes[match]; });
|
||||||
|
|
||||||
|
if (escape) {
|
||||||
|
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
|
||||||
|
}
|
||||||
|
if (interpolate) {
|
||||||
|
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
|
||||||
|
}
|
||||||
|
if (evaluate) {
|
||||||
|
source += "';\n" + evaluate + "\n__p+='";
|
||||||
|
}
|
||||||
|
index = offset + match.length;
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
source += "';\n";
|
||||||
|
|
||||||
|
// If a variable is not specified, place data values in local scope.
|
||||||
|
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
|
||||||
|
|
||||||
|
source = "var __t,__p='',__j=Array.prototype.join," +
|
||||||
|
"print=function(){__p+=__j.call(arguments,'');};\n" +
|
||||||
|
source + "return __p;\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
render = new Function(settings.variable || 'obj', '_', source);
|
||||||
|
} catch (e) {
|
||||||
|
e.source = source;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) return render(data, _);
|
||||||
|
var template = function(data) {
|
||||||
|
return render.call(this, data, _);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Provide the compiled function source as a convenience for precompilation.
|
||||||
|
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
return _;
|
||||||
|
})({});
|
||||||
|
|
||||||
|
if (location.hostname === 'todomvc.com') {
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||||
|
ga('create', 'UA-31081062-1', 'auto');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
}
|
||||||
|
/* jshint ignore:end */
|
||||||
|
|
||||||
|
function redirect() {
|
||||||
|
if (location.hostname === 'tastejs.github.io') {
|
||||||
|
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findRoot() {
|
||||||
|
var base = location.href.indexOf('examples/');
|
||||||
|
return location.href.substr(0, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFile(file, callback) {
|
||||||
|
if (!location.host) {
|
||||||
|
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.open('GET', findRoot() + file, true);
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
xhr.onload = function () {
|
||||||
|
if (xhr.status === 200 && callback) {
|
||||||
|
callback(xhr.responseText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function Learn(learnJSON, config) {
|
||||||
|
if (!(this instanceof Learn)) {
|
||||||
|
return new Learn(learnJSON, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
var template, framework;
|
||||||
|
|
||||||
|
if (typeof learnJSON !== 'object') {
|
||||||
|
try {
|
||||||
|
learnJSON = JSON.parse(learnJSON);
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config) {
|
||||||
|
template = config.template;
|
||||||
|
framework = config.framework;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!template && learnJSON.templates) {
|
||||||
|
template = learnJSON.templates.todomvc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!framework && document.querySelector('[data-framework]')) {
|
||||||
|
framework = document.querySelector('[data-framework]').dataset.framework;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.template = template;
|
||||||
|
|
||||||
|
if (learnJSON.backend) {
|
||||||
|
this.frameworkJSON = learnJSON.backend;
|
||||||
|
this.frameworkJSON.issueLabel = framework;
|
||||||
|
this.append({
|
||||||
|
backend: true
|
||||||
|
});
|
||||||
|
} else if (learnJSON[framework]) {
|
||||||
|
this.frameworkJSON = learnJSON[framework];
|
||||||
|
this.frameworkJSON.issueLabel = framework;
|
||||||
|
this.append();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchIssueCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
Learn.prototype.append = function (opts) {
|
||||||
|
var aside = document.createElement('aside');
|
||||||
|
aside.innerHTML = _.template(this.template, this.frameworkJSON);
|
||||||
|
aside.className = 'learn';
|
||||||
|
|
||||||
|
if (opts && opts.backend) {
|
||||||
|
// Remove demo link
|
||||||
|
var sourceLinks = aside.querySelector('.source-links');
|
||||||
|
var heading = sourceLinks.firstElementChild;
|
||||||
|
var sourceLink = sourceLinks.lastElementChild;
|
||||||
|
// Correct link path
|
||||||
|
var href = sourceLink.getAttribute('href');
|
||||||
|
sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
|
||||||
|
sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
|
||||||
|
} else {
|
||||||
|
// Localize demo links
|
||||||
|
var demoLinks = aside.querySelectorAll('.demo-link');
|
||||||
|
Array.prototype.forEach.call(demoLinks, function (demoLink) {
|
||||||
|
if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
|
||||||
|
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.className = (document.body.className + ' learn-bar').trim();
|
||||||
|
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
|
||||||
|
};
|
||||||
|
|
||||||
|
Learn.prototype.fetchIssueCount = function () {
|
||||||
|
var issueLink = document.getElementById('issue-count-link');
|
||||||
|
if (issueLink) {
|
||||||
|
var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.onload = function (e) {
|
||||||
|
var parsedResponse = JSON.parse(e.target.responseText);
|
||||||
|
if (parsedResponse instanceof Array) {
|
||||||
|
var count = parsedResponse.length;
|
||||||
|
if (count !== 0) {
|
||||||
|
issueLink.innerHTML = 'This app has ' + count + ' open issues';
|
||||||
|
document.getElementById('issue-count').style.display = 'inline';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
redirect();
|
||||||
|
getFile('learn.json', Learn);
|
||||||
|
})();
|
||||||
49
www/oldtodo/example/index.html
Normal file
49
www/oldtodo/example/index.html
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en" data-framework="javascript">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Crypt Todo</title>
|
||||||
|
<link rel="stylesheet" href="assets/todomvc-common/base.css">
|
||||||
|
<link rel="stylesheet" href="assets/todomvc-app-css/index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section class="todoapp">
|
||||||
|
<header class="header">
|
||||||
|
<h1>todos</h1>
|
||||||
|
<input class="new-todo" placeholder="What needs to be done?" autofocus>
|
||||||
|
</header>
|
||||||
|
<section class="main">
|
||||||
|
<input class="toggle-all" type="checkbox">
|
||||||
|
<label for="toggle-all">Mark all as complete</label>
|
||||||
|
<ul class="todo-list"></ul>
|
||||||
|
</section>
|
||||||
|
<footer class="footer">
|
||||||
|
<span class="todo-count"></span>
|
||||||
|
<ul class="filters">
|
||||||
|
<li>
|
||||||
|
<a href="#/" class="selected">All</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#/active">Active</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#/completed">Completed</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button class="clear-completed">Clear completed</button>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
|
<footer class="info">
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
<script src="assets/todomvc-common/base.js"></script>
|
||||||
|
<script src="js/helpers.js"></script>
|
||||||
|
<script src="js/store.js"></script>
|
||||||
|
<script src="js/model.js"></script>
|
||||||
|
<script src="js/template.js"></script>
|
||||||
|
<script src="js/view.js"></script>
|
||||||
|
<script src="js/controller.js"></script>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
25
www/oldtodo/example/js/app.js
Normal file
25
www/oldtodo/example/js/app.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*global app, $on */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a brand new Todo list.
|
||||||
|
*
|
||||||
|
* @param {string} name The name of your new to do list.
|
||||||
|
*/
|
||||||
|
function Todo(name) {
|
||||||
|
this.storage = new app.Store(name);
|
||||||
|
this.model = new app.Model(this.storage);
|
||||||
|
this.template = new app.Template();
|
||||||
|
this.view = new app.View(this.template);
|
||||||
|
this.controller = new app.Controller(this.model, this.view);
|
||||||
|
}
|
||||||
|
|
||||||
|
var todo = new Todo('todos-vanillajs');
|
||||||
|
|
||||||
|
function setView() {
|
||||||
|
todo.controller.setView(document.location.hash);
|
||||||
|
}
|
||||||
|
$on(window, 'load', setView);
|
||||||
|
$on(window, 'hashchange', setView);
|
||||||
|
})();
|
||||||
270
www/oldtodo/example/js/controller.js
Normal file
270
www/oldtodo/example/js/controller.js
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a model and view and acts as the controller between them
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {object} model The model instance
|
||||||
|
* @param {object} view The view instance
|
||||||
|
*/
|
||||||
|
function Controller(model, view) {
|
||||||
|
var self = this;
|
||||||
|
self.model = model;
|
||||||
|
self.view = view;
|
||||||
|
|
||||||
|
self.view.bind('newTodo', function (title) {
|
||||||
|
self.addItem(title);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('itemEdit', function (item) {
|
||||||
|
self.editItem(item.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('itemEditDone', function (item) {
|
||||||
|
self.editItemSave(item.id, item.title);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('itemEditCancel', function (item) {
|
||||||
|
self.editItemCancel(item.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('itemRemove', function (item) {
|
||||||
|
self.removeItem(item.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('itemToggle', function (item) {
|
||||||
|
self.toggleComplete(item.id, item.completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('removeCompleted', function () {
|
||||||
|
self.removeCompletedItems();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.bind('toggleAll', function (status) {
|
||||||
|
self.toggleAll(status.completed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads and initialises the view
|
||||||
|
*
|
||||||
|
* @param {string} '' | 'active' | 'completed'
|
||||||
|
*/
|
||||||
|
Controller.prototype.setView = function (locationHash) {
|
||||||
|
var route = locationHash.split('/')[1];
|
||||||
|
var page = route || '';
|
||||||
|
this._updateFilterState(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event to fire on load. Will get all items and display them in the
|
||||||
|
* todo-list
|
||||||
|
*/
|
||||||
|
Controller.prototype.showAll = function () {
|
||||||
|
var self = this;
|
||||||
|
self.model.read(function (data) {
|
||||||
|
self.view.render('showEntries', data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders all active tasks
|
||||||
|
*/
|
||||||
|
Controller.prototype.showActive = function () {
|
||||||
|
var self = this;
|
||||||
|
self.model.read({ completed: false }, function (data) {
|
||||||
|
self.view.render('showEntries', data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders all completed tasks
|
||||||
|
*/
|
||||||
|
Controller.prototype.showCompleted = function () {
|
||||||
|
var self = this;
|
||||||
|
self.model.read({ completed: true }, function (data) {
|
||||||
|
self.view.render('showEntries', data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event to fire whenever you want to add an item. Simply pass in the event
|
||||||
|
* object and it'll handle the DOM insertion and saving of the new item.
|
||||||
|
*/
|
||||||
|
Controller.prototype.addItem = function (title) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (title.trim() === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.model.create(title, function () {
|
||||||
|
self.view.render('clearNewTodo');
|
||||||
|
self._filter(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Triggers the item editing mode.
|
||||||
|
*/
|
||||||
|
Controller.prototype.editItem = function (id) {
|
||||||
|
var self = this;
|
||||||
|
self.model.read(id, function (data) {
|
||||||
|
self.view.render('editItem', {id: id, title: data[0].title});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Finishes the item editing mode successfully.
|
||||||
|
*/
|
||||||
|
Controller.prototype.editItemSave = function (id, title) {
|
||||||
|
var self = this;
|
||||||
|
title = title.trim();
|
||||||
|
|
||||||
|
if (title.length !== 0) {
|
||||||
|
self.model.update(id, {title: title}, function () {
|
||||||
|
self.view.render('editItemDone', {id: id, title: title});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.removeItem(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cancels the item editing mode.
|
||||||
|
*/
|
||||||
|
Controller.prototype.editItemCancel = function (id) {
|
||||||
|
var self = this;
|
||||||
|
self.model.read(id, function (data) {
|
||||||
|
self.view.render('editItemDone', {id: id, title: data[0].title});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By giving it an ID it'll find the DOM element matching that ID,
|
||||||
|
* remove it from the DOM and also remove it from storage.
|
||||||
|
*
|
||||||
|
* @param {number} id The ID of the item to remove from the DOM and
|
||||||
|
* storage
|
||||||
|
*/
|
||||||
|
Controller.prototype.removeItem = function (id) {
|
||||||
|
var self = this;
|
||||||
|
self.model.remove(id, function () {
|
||||||
|
self.view.render('removeItem', id);
|
||||||
|
});
|
||||||
|
|
||||||
|
self._filter();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will remove all completed items from the DOM and storage.
|
||||||
|
*/
|
||||||
|
Controller.prototype.removeCompletedItems = function () {
|
||||||
|
var self = this;
|
||||||
|
self.model.read({ completed: true }, function (data) {
|
||||||
|
data.forEach(function (item) {
|
||||||
|
self.removeItem(item.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
self._filter();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give it an ID of a model and a checkbox and it will update the item
|
||||||
|
* in storage based on the checkbox's state.
|
||||||
|
*
|
||||||
|
* @param {number} id The ID of the element to complete or uncomplete
|
||||||
|
* @param {object} checkbox The checkbox to check the state of complete
|
||||||
|
* or not
|
||||||
|
* @param {boolean|undefined} silent Prevent re-filtering the todo items
|
||||||
|
*/
|
||||||
|
Controller.prototype.toggleComplete = function (id, completed, silent) {
|
||||||
|
var self = this;
|
||||||
|
self.model.update(id, { completed: completed }, function () {
|
||||||
|
self.view.render('elementComplete', {
|
||||||
|
id: id,
|
||||||
|
completed: completed
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!silent) {
|
||||||
|
self._filter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will toggle ALL checkboxes' on/off state and completeness of models.
|
||||||
|
* Just pass in the event object.
|
||||||
|
*/
|
||||||
|
Controller.prototype.toggleAll = function (completed) {
|
||||||
|
var self = this;
|
||||||
|
self.model.read({ completed: !completed }, function (data) {
|
||||||
|
data.forEach(function (item) {
|
||||||
|
self.toggleComplete(item.id, completed, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
self._filter();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the pieces of the page which change depending on the remaining
|
||||||
|
* number of todos.
|
||||||
|
*/
|
||||||
|
Controller.prototype._updateCount = function () {
|
||||||
|
var self = this;
|
||||||
|
self.model.getCount(function (todos) {
|
||||||
|
self.view.render('updateElementCount', todos.active);
|
||||||
|
self.view.render('clearCompletedButton', {
|
||||||
|
completed: todos.completed,
|
||||||
|
visible: todos.completed > 0
|
||||||
|
});
|
||||||
|
|
||||||
|
self.view.render('toggleAll', {checked: todos.completed === todos.total});
|
||||||
|
self.view.render('contentBlockVisibility', {visible: todos.total > 0});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-filters the todo items, based on the active route.
|
||||||
|
* @param {boolean|undefined} force forces a re-painting of todo items.
|
||||||
|
*/
|
||||||
|
Controller.prototype._filter = function (force) {
|
||||||
|
var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1);
|
||||||
|
|
||||||
|
// Update the elements on the page, which change with each completed todo
|
||||||
|
this._updateCount();
|
||||||
|
|
||||||
|
// If the last active route isn't "All", or we're switching routes, we
|
||||||
|
// re-create the todo item elements, calling:
|
||||||
|
// this.show[All|Active|Completed]();
|
||||||
|
if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) {
|
||||||
|
this['show' + activeRoute]();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._lastActiveRoute = activeRoute;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply updates the filter nav's selected states
|
||||||
|
*/
|
||||||
|
Controller.prototype._updateFilterState = function (currentPage) {
|
||||||
|
// Store a reference to the active route, allowing us to re-filter todo
|
||||||
|
// items as they are marked complete or incomplete.
|
||||||
|
this._activeRoute = currentPage;
|
||||||
|
|
||||||
|
if (currentPage === '') {
|
||||||
|
this._activeRoute = 'All';
|
||||||
|
}
|
||||||
|
|
||||||
|
this._filter();
|
||||||
|
|
||||||
|
this.view.render('setFilter', currentPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export to window
|
||||||
|
window.app = window.app || {};
|
||||||
|
window.app.Controller = Controller;
|
||||||
|
})(window);
|
||||||
52
www/oldtodo/example/js/helpers.js
Normal file
52
www/oldtodo/example/js/helpers.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*global NodeList */
|
||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Get element(s) by CSS selector:
|
||||||
|
window.qs = function (selector, scope) {
|
||||||
|
return (scope || document).querySelector(selector);
|
||||||
|
};
|
||||||
|
window.qsa = function (selector, scope) {
|
||||||
|
return (scope || document).querySelectorAll(selector);
|
||||||
|
};
|
||||||
|
|
||||||
|
// addEventListener wrapper:
|
||||||
|
window.$on = function (target, type, callback, useCapture) {
|
||||||
|
target.addEventListener(type, callback, !!useCapture);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach a handler to event for all elements that match the selector,
|
||||||
|
// now or in the future, based on a root element
|
||||||
|
window.$delegate = function (target, selector, type, handler) {
|
||||||
|
function dispatchEvent(event) {
|
||||||
|
var targetElement = event.target;
|
||||||
|
var potentialElements = window.qsa(selector, target);
|
||||||
|
var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;
|
||||||
|
|
||||||
|
if (hasMatch) {
|
||||||
|
handler.call(targetElement, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/Events/blur
|
||||||
|
var useCapture = type === 'blur' || type === 'focus';
|
||||||
|
|
||||||
|
window.$on(target, type, dispatchEvent, useCapture);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the element's parent with the given tag name:
|
||||||
|
// $parent(qs('a'), 'div');
|
||||||
|
window.$parent = function (element, tagName) {
|
||||||
|
if (!element.parentNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) {
|
||||||
|
return element.parentNode;
|
||||||
|
}
|
||||||
|
return window.$parent(element.parentNode, tagName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allow for looping on nodes by chaining:
|
||||||
|
// qsa('.foo').forEach(function () {})
|
||||||
|
NodeList.prototype.forEach = Array.prototype.forEach;
|
||||||
|
})(window);
|
||||||
120
www/oldtodo/example/js/model.js
Normal file
120
www/oldtodo/example/js/model.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Model instance and hooks up the storage.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {object} storage A reference to the client side storage class
|
||||||
|
*/
|
||||||
|
function Model(storage) {
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new todo model
|
||||||
|
*
|
||||||
|
* @param {string} [title] The title of the task
|
||||||
|
* @param {function} [callback] The callback to fire after the model is created
|
||||||
|
*/
|
||||||
|
Model.prototype.create = function (title, callback) {
|
||||||
|
title = title || '';
|
||||||
|
callback = callback || function () {};
|
||||||
|
|
||||||
|
var newItem = {
|
||||||
|
title: title.trim(),
|
||||||
|
completed: false
|
||||||
|
};
|
||||||
|
|
||||||
|
this.storage.save(newItem, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds and returns a model in storage. If no query is given it'll simply
|
||||||
|
* return everything. If you pass in a string or number it'll look that up as
|
||||||
|
* the ID of the model to find. Lastly, you can pass it an object to match
|
||||||
|
* against.
|
||||||
|
*
|
||||||
|
* @param {string|number|object} [query] A query to match models against
|
||||||
|
* @param {function} [callback] The callback to fire after the model is found
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* model.read(1, func); // Will find the model with an ID of 1
|
||||||
|
* model.read('1'); // Same as above
|
||||||
|
* //Below will find a model with foo equalling bar and hello equalling world.
|
||||||
|
* model.read({ foo: 'bar', hello: 'world' });
|
||||||
|
*/
|
||||||
|
Model.prototype.read = function (query, callback) {
|
||||||
|
var queryType = typeof query;
|
||||||
|
callback = callback || function () {};
|
||||||
|
|
||||||
|
if (queryType === 'function') {
|
||||||
|
callback = query;
|
||||||
|
return this.storage.findAll(callback);
|
||||||
|
} else if (queryType === 'string' || queryType === 'number') {
|
||||||
|
query = parseInt(query, 10);
|
||||||
|
this.storage.find({ id: query }, callback);
|
||||||
|
} else {
|
||||||
|
this.storage.find(query, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a model by giving it an ID, data to update, and a callback to fire when
|
||||||
|
* the update is complete.
|
||||||
|
*
|
||||||
|
* @param {number} id The id of the model to update
|
||||||
|
* @param {object} data The properties to update and their new value
|
||||||
|
* @param {function} callback The callback to fire when the update is complete.
|
||||||
|
*/
|
||||||
|
Model.prototype.update = function (id, data, callback) {
|
||||||
|
this.storage.save(data, callback, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a model from storage
|
||||||
|
*
|
||||||
|
* @param {number} id The ID of the model to remove
|
||||||
|
* @param {function} callback The callback to fire when the removal is complete.
|
||||||
|
*/
|
||||||
|
Model.prototype.remove = function (id, callback) {
|
||||||
|
this.storage.remove(id, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: Will remove ALL data from storage.
|
||||||
|
*
|
||||||
|
* @param {function} callback The callback to fire when the storage is wiped.
|
||||||
|
*/
|
||||||
|
Model.prototype.removeAll = function (callback) {
|
||||||
|
this.storage.drop(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a count of all todos
|
||||||
|
*/
|
||||||
|
Model.prototype.getCount = function (callback) {
|
||||||
|
var todos = {
|
||||||
|
active: 0,
|
||||||
|
completed: 0,
|
||||||
|
total: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.storage.findAll(function (data) {
|
||||||
|
data.forEach(function (todo) {
|
||||||
|
if (todo.completed) {
|
||||||
|
todos.completed++;
|
||||||
|
} else {
|
||||||
|
todos.active++;
|
||||||
|
}
|
||||||
|
|
||||||
|
todos.total++;
|
||||||
|
});
|
||||||
|
callback(todos);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export to window
|
||||||
|
window.app = window.app || {};
|
||||||
|
window.app.Model = Model;
|
||||||
|
})(window);
|
||||||
141
www/oldtodo/example/js/store.js
Normal file
141
www/oldtodo/example/js/store.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*jshint eqeqeq:false */
|
||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new client side storage object and will create an empty
|
||||||
|
* collection if no collection already exists.
|
||||||
|
*
|
||||||
|
* @param {string} name The name of our DB we want to use
|
||||||
|
* @param {function} callback Our fake DB uses callbacks because in
|
||||||
|
* real life you probably would be making AJAX calls
|
||||||
|
*/
|
||||||
|
function Store(name, callback) {
|
||||||
|
callback = callback || function () {};
|
||||||
|
|
||||||
|
this._dbName = name;
|
||||||
|
|
||||||
|
if (!localStorage[name]) {
|
||||||
|
var data = {
|
||||||
|
todos: []
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage[name] = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback.call(this, JSON.parse(localStorage[name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds items based on a query given as a JS object
|
||||||
|
*
|
||||||
|
* @param {object} query The query to match against (i.e. {foo: 'bar'})
|
||||||
|
* @param {function} callback The callback to fire when the query has
|
||||||
|
* completed running
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* db.find({foo: 'bar', hello: 'world'}, function (data) {
|
||||||
|
* // data will return any items that have foo: bar and
|
||||||
|
* // hello: world in their properties
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
Store.prototype.find = function (query, callback) {
|
||||||
|
if (!callback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var todos = JSON.parse(localStorage[this._dbName]).todos;
|
||||||
|
|
||||||
|
callback.call(this, todos.filter(function (todo) {
|
||||||
|
for (var q in query) {
|
||||||
|
if (query[q] !== todo[q]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will retrieve all data from the collection
|
||||||
|
*
|
||||||
|
* @param {function} callback The callback to fire upon retrieving data
|
||||||
|
*/
|
||||||
|
Store.prototype.findAll = function (callback) {
|
||||||
|
callback = callback || function () {};
|
||||||
|
callback.call(this, JSON.parse(localStorage[this._dbName]).todos);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will save the given data to the DB. If no item exists it will create a new
|
||||||
|
* item, otherwise it'll simply update an existing item's properties
|
||||||
|
*
|
||||||
|
* @param {object} updateData The data to save back into the DB
|
||||||
|
* @param {function} callback The callback to fire after saving
|
||||||
|
* @param {number} id An optional param to enter an ID of an item to update
|
||||||
|
*/
|
||||||
|
Store.prototype.save = function (updateData, callback, id) {
|
||||||
|
var data = JSON.parse(localStorage[this._dbName]);
|
||||||
|
var todos = data.todos;
|
||||||
|
|
||||||
|
callback = callback || function () {};
|
||||||
|
|
||||||
|
// If an ID was actually given, find the item and update each property
|
||||||
|
if (id) {
|
||||||
|
for (var i = 0; i < todos.length; i++) {
|
||||||
|
if (todos[i].id === id) {
|
||||||
|
for (var key in updateData) {
|
||||||
|
todos[i][key] = updateData[key];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage[this._dbName] = JSON.stringify(data);
|
||||||
|
callback.call(this, todos);
|
||||||
|
} else {
|
||||||
|
// Generate an ID
|
||||||
|
updateData.id = new Date().getTime();
|
||||||
|
|
||||||
|
todos.push(updateData);
|
||||||
|
localStorage[this._dbName] = JSON.stringify(data);
|
||||||
|
callback.call(this, [updateData]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will remove an item from the Store based on its ID
|
||||||
|
*
|
||||||
|
* @param {number} id The ID of the item you want to remove
|
||||||
|
* @param {function} callback The callback to fire after saving
|
||||||
|
*/
|
||||||
|
Store.prototype.remove = function (id, callback) {
|
||||||
|
var data = JSON.parse(localStorage[this._dbName]);
|
||||||
|
var todos = data.todos;
|
||||||
|
|
||||||
|
for (var i = 0; i < todos.length; i++) {
|
||||||
|
if (todos[i].id == id) {
|
||||||
|
todos.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage[this._dbName] = JSON.stringify(data);
|
||||||
|
callback.call(this, todos);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will drop all storage and start fresh
|
||||||
|
*
|
||||||
|
* @param {function} callback The callback to fire after dropping the data
|
||||||
|
*/
|
||||||
|
Store.prototype.drop = function (callback) {
|
||||||
|
var data = {todos: []};
|
||||||
|
localStorage[this._dbName] = JSON.stringify(data);
|
||||||
|
callback.call(this, data.todos);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export to window
|
||||||
|
window.app = window.app || {};
|
||||||
|
window.app.Store = Store;
|
||||||
|
})(window);
|
||||||
114
www/oldtodo/example/js/template.js
Normal file
114
www/oldtodo/example/js/template.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/*jshint laxbreak:true */
|
||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var htmlEscapes = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
'\'': ''',
|
||||||
|
'`': '`'
|
||||||
|
};
|
||||||
|
|
||||||
|
var escapeHtmlChar = function (chr) {
|
||||||
|
return htmlEscapes[chr];
|
||||||
|
};
|
||||||
|
|
||||||
|
var reUnescapedHtml = /[&<>"'`]/g;
|
||||||
|
var reHasUnescapedHtml = new RegExp(reUnescapedHtml.source);
|
||||||
|
|
||||||
|
var escape = function (string) {
|
||||||
|
return (string && reHasUnescapedHtml.test(string))
|
||||||
|
? string.replace(reUnescapedHtml, escapeHtmlChar)
|
||||||
|
: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up defaults for all the Template methods such as a default template
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Template() {
|
||||||
|
this.defaultTemplate
|
||||||
|
= '<li data-id="{{id}}" class="{{completed}}">'
|
||||||
|
+ '<div class="view">'
|
||||||
|
+ '<input class="toggle" type="checkbox" {{checked}}>'
|
||||||
|
+ '<label>{{title}}</label>'
|
||||||
|
+ '<button class="destroy"></button>'
|
||||||
|
+ '</div>'
|
||||||
|
+ '</li>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an <li> HTML string and returns it for placement in your app.
|
||||||
|
*
|
||||||
|
* NOTE: In real life you should be using a templating engine such as Mustache
|
||||||
|
* or Handlebars, however, this is a vanilla JS example.
|
||||||
|
*
|
||||||
|
* @param {object} data The object containing keys you want to find in the
|
||||||
|
* template to replace.
|
||||||
|
* @returns {string} HTML String of an <li> element
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* view.show({
|
||||||
|
* id: 1,
|
||||||
|
* title: "Hello World",
|
||||||
|
* completed: 0,
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
Template.prototype.show = function (data) {
|
||||||
|
var i = 0, l = data.length;
|
||||||
|
var view = '';
|
||||||
|
|
||||||
|
for (; i < l; i++) {
|
||||||
|
var template = this.defaultTemplate;
|
||||||
|
var completed = '';
|
||||||
|
var checked = '';
|
||||||
|
|
||||||
|
if (data[i].completed) {
|
||||||
|
completed = 'completed';
|
||||||
|
checked = 'checked';
|
||||||
|
}
|
||||||
|
|
||||||
|
template = template.replace('{{id}}', data[i].id);
|
||||||
|
template = template.replace('{{title}}', escape(data[i].title));
|
||||||
|
template = template.replace('{{completed}}', completed);
|
||||||
|
template = template.replace('{{checked}}', checked);
|
||||||
|
|
||||||
|
view = view + template;
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a counter of how many to dos are left to complete
|
||||||
|
*
|
||||||
|
* @param {number} activeTodos The number of active todos.
|
||||||
|
* @returns {string} String containing the count
|
||||||
|
*/
|
||||||
|
Template.prototype.itemCounter = function (activeTodos) {
|
||||||
|
var plural = activeTodos === 1 ? '' : 's';
|
||||||
|
|
||||||
|
return '<strong>' + activeTodos + '</strong> item' + plural + ' left';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the text within the "Clear completed" button
|
||||||
|
*
|
||||||
|
* @param {[type]} completedTodos The number of completed todos.
|
||||||
|
* @returns {string} String containing the count
|
||||||
|
*/
|
||||||
|
Template.prototype.clearCompletedButton = function (completedTodos) {
|
||||||
|
if (completedTodos > 0) {
|
||||||
|
return 'Clear completed';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export to window
|
||||||
|
window.app = window.app || {};
|
||||||
|
window.app.Template = Template;
|
||||||
|
})(window);
|
||||||
219
www/oldtodo/example/js/view.js
Normal file
219
www/oldtodo/example/js/view.js
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
/*global qs, qsa, $on, $parent, $delegate */
|
||||||
|
|
||||||
|
(function (window) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View that abstracts away the browser's DOM completely.
|
||||||
|
* It has two simple entry points:
|
||||||
|
*
|
||||||
|
* - bind(eventName, handler)
|
||||||
|
* Takes a todo application event and registers the handler
|
||||||
|
* - render(command, parameterObject)
|
||||||
|
* Renders the given command with the options
|
||||||
|
*/
|
||||||
|
function View(template) {
|
||||||
|
this.template = template;
|
||||||
|
|
||||||
|
this.ENTER_KEY = 13;
|
||||||
|
this.ESCAPE_KEY = 27;
|
||||||
|
|
||||||
|
this.$todoList = qs('.todo-list');
|
||||||
|
this.$todoItemCounter = qs('.todo-count');
|
||||||
|
this.$clearCompleted = qs('.clear-completed');
|
||||||
|
this.$main = qs('.main');
|
||||||
|
this.$footer = qs('.footer');
|
||||||
|
this.$toggleAll = qs('.toggle-all');
|
||||||
|
this.$newTodo = qs('.new-todo');
|
||||||
|
}
|
||||||
|
|
||||||
|
View.prototype._removeItem = function (id) {
|
||||||
|
var elem = qs('[data-id="' + id + '"]');
|
||||||
|
|
||||||
|
if (elem) {
|
||||||
|
this.$todoList.removeChild(elem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._clearCompletedButton = function (completedCount, visible) {
|
||||||
|
this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount);
|
||||||
|
this.$clearCompleted.style.display = visible ? 'block' : 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._setFilter = function (currentPage) {
|
||||||
|
qs('.filters .selected').className = '';
|
||||||
|
qs('.filters [href="#/' + currentPage + '"]').className = 'selected';
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._elementComplete = function (id, completed) {
|
||||||
|
var listItem = qs('[data-id="' + id + '"]');
|
||||||
|
|
||||||
|
if (!listItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listItem.className = completed ? 'completed' : '';
|
||||||
|
|
||||||
|
// In case it was toggled from an event and not by clicking the checkbox
|
||||||
|
qs('input', listItem).checked = completed;
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._editItem = function (id, title) {
|
||||||
|
var listItem = qs('[data-id="' + id + '"]');
|
||||||
|
|
||||||
|
if (!listItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listItem.className = listItem.className + ' editing';
|
||||||
|
|
||||||
|
var input = document.createElement('input');
|
||||||
|
input.className = 'edit';
|
||||||
|
|
||||||
|
listItem.appendChild(input);
|
||||||
|
input.focus();
|
||||||
|
input.value = title;
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._editItemDone = function (id, title) {
|
||||||
|
var listItem = qs('[data-id="' + id + '"]');
|
||||||
|
|
||||||
|
if (!listItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var input = qs('input.edit', listItem);
|
||||||
|
listItem.removeChild(input);
|
||||||
|
|
||||||
|
listItem.className = listItem.className.replace('editing', '');
|
||||||
|
|
||||||
|
qsa('label', listItem).forEach(function (label) {
|
||||||
|
label.textContent = title;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype.render = function (viewCmd, parameter) {
|
||||||
|
var self = this;
|
||||||
|
var viewCommands = {
|
||||||
|
showEntries: function () {
|
||||||
|
self.$todoList.innerHTML = self.template.show(parameter);
|
||||||
|
},
|
||||||
|
removeItem: function () {
|
||||||
|
self._removeItem(parameter);
|
||||||
|
},
|
||||||
|
updateElementCount: function () {
|
||||||
|
self.$todoItemCounter.innerHTML = self.template.itemCounter(parameter);
|
||||||
|
},
|
||||||
|
clearCompletedButton: function () {
|
||||||
|
self._clearCompletedButton(parameter.completed, parameter.visible);
|
||||||
|
},
|
||||||
|
contentBlockVisibility: function () {
|
||||||
|
self.$main.style.display = self.$footer.style.display = parameter.visible ? 'block' : 'none';
|
||||||
|
},
|
||||||
|
toggleAll: function () {
|
||||||
|
self.$toggleAll.checked = parameter.checked;
|
||||||
|
},
|
||||||
|
setFilter: function () {
|
||||||
|
self._setFilter(parameter);
|
||||||
|
},
|
||||||
|
clearNewTodo: function () {
|
||||||
|
self.$newTodo.value = '';
|
||||||
|
},
|
||||||
|
elementComplete: function () {
|
||||||
|
self._elementComplete(parameter.id, parameter.completed);
|
||||||
|
},
|
||||||
|
editItem: function () {
|
||||||
|
self._editItem(parameter.id, parameter.title);
|
||||||
|
},
|
||||||
|
editItemDone: function () {
|
||||||
|
self._editItemDone(parameter.id, parameter.title);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
viewCommands[viewCmd]();
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._itemId = function (element) {
|
||||||
|
var li = $parent(element, 'li');
|
||||||
|
return parseInt(li.dataset.id, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._bindItemEditDone = function (handler) {
|
||||||
|
var self = this;
|
||||||
|
$delegate(self.$todoList, 'li .edit', 'blur', function () {
|
||||||
|
if (!this.dataset.iscanceled) {
|
||||||
|
handler({
|
||||||
|
id: self._itemId(this),
|
||||||
|
title: this.value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$delegate(self.$todoList, 'li .edit', 'keypress', function (event) {
|
||||||
|
if (event.keyCode === self.ENTER_KEY) {
|
||||||
|
// Remove the cursor from the input when you hit enter just like if it
|
||||||
|
// were a real form
|
||||||
|
this.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype._bindItemEditCancel = function (handler) {
|
||||||
|
var self = this;
|
||||||
|
$delegate(self.$todoList, 'li .edit', 'keyup', function (event) {
|
||||||
|
if (event.keyCode === self.ESCAPE_KEY) {
|
||||||
|
this.dataset.iscanceled = true;
|
||||||
|
this.blur();
|
||||||
|
|
||||||
|
handler({id: self._itemId(this)});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype.bind = function (event, handler) {
|
||||||
|
var self = this;
|
||||||
|
if (event === 'newTodo') {
|
||||||
|
$on(self.$newTodo, 'change', function () {
|
||||||
|
handler(self.$newTodo.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (event === 'removeCompleted') {
|
||||||
|
$on(self.$clearCompleted, 'click', function () {
|
||||||
|
handler();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (event === 'toggleAll') {
|
||||||
|
$on(self.$toggleAll, 'click', function () {
|
||||||
|
handler({completed: this.checked});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (event === 'itemEdit') {
|
||||||
|
$delegate(self.$todoList, 'li label', 'dblclick', function () {
|
||||||
|
handler({id: self._itemId(this)});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (event === 'itemRemove') {
|
||||||
|
$delegate(self.$todoList, '.destroy', 'click', function () {
|
||||||
|
handler({id: self._itemId(this)});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (event === 'itemToggle') {
|
||||||
|
$delegate(self.$todoList, '.toggle', 'click', function () {
|
||||||
|
handler({
|
||||||
|
id: self._itemId(this),
|
||||||
|
completed: this.checked
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (event === 'itemEditDone') {
|
||||||
|
self._bindItemEditDone(handler);
|
||||||
|
|
||||||
|
} else if (event === 'itemEditCancel') {
|
||||||
|
self._bindItemEditCancel(handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export to window
|
||||||
|
window.app = window.app || {};
|
||||||
|
window.app.View = View;
|
||||||
|
}(window));
|
||||||
30
www/oldtodo/index.html
Normal file
30
www/oldtodo/index.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="cp pad">
|
||||||
|
<head>
|
||||||
|
<title>CryptPad</title>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
|
<style>
|
||||||
|
html, body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#pad-iframe {
|
||||||
|
position:fixed;
|
||||||
|
top:0px;
|
||||||
|
left:0px;
|
||||||
|
bottom:0px;
|
||||||
|
right:0px;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
border:none;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||||
|
|
||||||
20
www/oldtodo/inner.html
Normal file
20
www/oldtodo/inner.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||||
|
<script async data-bootload="/todo/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
|
<style>.loading-hidden, .loading-hidden * {display: none !important;}</style>
|
||||||
|
</head>
|
||||||
|
<body class="loading-hidden">
|
||||||
|
<div id="toolbar" class="toolbar-container"></div>
|
||||||
|
<div id="container">
|
||||||
|
<div class="cp-create-form">
|
||||||
|
<input type="text" id="newTodoName" data-localization-placeholder="todo_newTodoNamePlaceholder" />
|
||||||
|
<button class="btn btn-success fa fa-plus" data-localization-title="todo_newTodoNameTitle"></button>
|
||||||
|
</div>
|
||||||
|
<div id="tasksList"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
15
www/oldtodo/inner.js
Normal file
15
www/oldtodo/inner.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||||
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
|
'less!/todo/todo.less',
|
||||||
|
//'less!/customize/src/less/cryptpad.less',
|
||||||
|
'less!/customize/src/less/toolbar.less',
|
||||||
|
], function ($) {
|
||||||
|
$('.loading-hidden').removeClass('loading-hidden');
|
||||||
|
// dirty hack to get rid the flash of the lock background
|
||||||
|
/*
|
||||||
|
setTimeout(function () {
|
||||||
|
$('#app').addClass('ready');
|
||||||
|
}, 100);*/
|
||||||
|
});
|
||||||
229
www/oldtodo/main.js
Normal file
229
www/oldtodo/main.js
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||||
|
'/common/toolbar2.js',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
'/todo/todo.js',
|
||||||
|
|
||||||
|
//'/common/media-tag.js',
|
||||||
|
//'/bower_components/file-saver/FileSaver.min.js',
|
||||||
|
|
||||||
|
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||||
|
'less!/customize/src/less/cryptpad.less',
|
||||||
|
], function ($, Crypto, Listmap, Toolbar, Cryptpad, Todo) {
|
||||||
|
var Messages = Cryptpad.Messages;
|
||||||
|
|
||||||
|
var APP = window.APP = {};
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
var $iframe = $('#pad-iframe').contents();
|
||||||
|
var $body = $iframe.find('body');
|
||||||
|
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||||
|
var $list = $iframe.find('#tasksList');
|
||||||
|
|
||||||
|
var removeTips = function () {
|
||||||
|
Cryptpad.clearTooltips();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onReady = function () {
|
||||||
|
|
||||||
|
var todo = Todo.init(APP.lm.proxy, Cryptpad);
|
||||||
|
|
||||||
|
var deleteTask = function(id) {
|
||||||
|
todo.remove(id);
|
||||||
|
|
||||||
|
var $els = $list.find('.cp-task').filter(function (i, el) {
|
||||||
|
return $(el).data('id') === id;
|
||||||
|
});
|
||||||
|
$els.fadeOut(null, function () {
|
||||||
|
$els.remove();
|
||||||
|
removeTips();
|
||||||
|
});
|
||||||
|
//APP.display();
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO make this actually work, and scroll to bottom...
|
||||||
|
var scrollTo = function (t) {
|
||||||
|
var $list = $iframe.find('#tasksList');
|
||||||
|
|
||||||
|
$list.animate({
|
||||||
|
scrollTop: t,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
scrollTo = scrollTo;
|
||||||
|
|
||||||
|
var makeCheckbox = function (id, cb) {
|
||||||
|
var entry = APP.lm.proxy.data[id];
|
||||||
|
var checked = entry.state === 1? 'cp-task-checkbox-checked fa-check-square-o': 'cp-task-checkbox-unchecked fa-square-o';
|
||||||
|
|
||||||
|
var title = entry.state === 1?
|
||||||
|
Messages.todo_markAsIncompleteTitle:
|
||||||
|
Messages.todo_markAsCompleteTitle;
|
||||||
|
title = title;
|
||||||
|
|
||||||
|
removeTips();
|
||||||
|
return $('<span>', {
|
||||||
|
'class': 'cp-task-checkbox fa ' + checked,
|
||||||
|
//title: title,
|
||||||
|
}).on('click', function () {
|
||||||
|
entry.state = (entry.state + 1) % 2;
|
||||||
|
if (typeof(cb) === 'function') {
|
||||||
|
cb(entry.state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var addTaskUI = function (el, animate) {
|
||||||
|
var $taskDiv = $('<div>', {
|
||||||
|
'class': 'cp-task'
|
||||||
|
});
|
||||||
|
if (animate) {
|
||||||
|
$taskDiv.prependTo($list);
|
||||||
|
} else {
|
||||||
|
$taskDiv.appendTo($list);
|
||||||
|
}
|
||||||
|
$taskDiv.data('id', el);
|
||||||
|
|
||||||
|
makeCheckbox(el, function (/*state*/) {
|
||||||
|
APP.display();
|
||||||
|
})
|
||||||
|
.appendTo($taskDiv);
|
||||||
|
|
||||||
|
var entry = APP.lm.proxy.data[el];
|
||||||
|
|
||||||
|
if (entry.state) {
|
||||||
|
$taskDiv.addClass('cp-task-complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('<span>', { 'class': 'cp-task-text' })
|
||||||
|
.text(entry.task)
|
||||||
|
.appendTo($taskDiv);
|
||||||
|
/*$('<span>', { 'class': 'cp-task-date' })
|
||||||
|
.text(new Date(entry.ctime).toLocaleString())
|
||||||
|
.appendTo($taskDiv);*/
|
||||||
|
$('<button>', {
|
||||||
|
'class': 'fa fa-times cp-task-remove btn btn-danger',
|
||||||
|
title: Messages.todo_removeTaskTitle,
|
||||||
|
}).appendTo($taskDiv).on('click', function() {
|
||||||
|
deleteTask(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
$taskDiv.hide();
|
||||||
|
window.setTimeout(function () {
|
||||||
|
// ???
|
||||||
|
$taskDiv.fadeIn();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
removeTips();
|
||||||
|
};
|
||||||
|
var display = APP.display = function () {
|
||||||
|
$list.empty();
|
||||||
|
removeTips();
|
||||||
|
APP.lm.proxy.order.forEach(function (el) {
|
||||||
|
addTaskUI(el);
|
||||||
|
});
|
||||||
|
//scrollTo('300px');
|
||||||
|
};
|
||||||
|
|
||||||
|
var addTask = function () {
|
||||||
|
var $input = $iframe.find('#newTodoName');
|
||||||
|
// if the input is empty after removing leading and trailing spaces
|
||||||
|
// don't create a new entry
|
||||||
|
if (!$input.val().trim()) { return; }
|
||||||
|
|
||||||
|
var obj = {
|
||||||
|
"state": 0,
|
||||||
|
"task": $input.val(),
|
||||||
|
"ctime": +new Date(),
|
||||||
|
"mtime": +new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = Cryptpad.createChannelId();
|
||||||
|
todo.add(id, obj);
|
||||||
|
|
||||||
|
$input.val("");
|
||||||
|
addTaskUI(id, true);
|
||||||
|
//display();
|
||||||
|
};
|
||||||
|
|
||||||
|
var $formSubmit = $iframe.find('.cp-create-form button').on('click', addTask);
|
||||||
|
$iframe.find('#newTodoName').on('keypress', function (e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case 13:
|
||||||
|
$formSubmit.click();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(e.which);
|
||||||
|
}
|
||||||
|
}).focus();
|
||||||
|
|
||||||
|
var editTask = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
editTask = editTask;
|
||||||
|
|
||||||
|
display();
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onInit = function () {
|
||||||
|
Cryptpad.addLoadingScreen();
|
||||||
|
|
||||||
|
$body.on('dragover', function (e) { e.preventDefault(); });
|
||||||
|
$body.on('drop', function (e) { e.preventDefault(); });
|
||||||
|
|
||||||
|
var Title;
|
||||||
|
var $bar = $iframe.find('.toolbar-container');
|
||||||
|
|
||||||
|
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
|
||||||
|
|
||||||
|
var configTb = {
|
||||||
|
displayed: ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'],
|
||||||
|
ifrw: ifrw,
|
||||||
|
common: Cryptpad,
|
||||||
|
//hideDisplayName: true,
|
||||||
|
$container: $bar,
|
||||||
|
pageTitle: Messages.todo_title
|
||||||
|
};
|
||||||
|
|
||||||
|
APP.toolbar = Toolbar.create(configTb);
|
||||||
|
APP.toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
|
||||||
|
};
|
||||||
|
|
||||||
|
var createTodo = function() {
|
||||||
|
var obj = Cryptpad.getProxy();
|
||||||
|
var hash = Cryptpad.createRandomHash();
|
||||||
|
|
||||||
|
if(obj.todo) {
|
||||||
|
hash = obj.todo;
|
||||||
|
} else {
|
||||||
|
obj.todo = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
var secret = Cryptpad.getSecrets('todo', hash);
|
||||||
|
|
||||||
|
var listmapConfig = {
|
||||||
|
data: {},
|
||||||
|
websocketURL: Cryptpad.getWebsocketURL(),
|
||||||
|
channel: secret.channel,
|
||||||
|
validateKey: secret.keys.validateKey || undefined,
|
||||||
|
crypto: Crypto.createEncryptor(secret.keys),
|
||||||
|
userName: 'todo',
|
||||||
|
logLevel: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
var lm = APP.lm = Listmap.create(listmapConfig);
|
||||||
|
|
||||||
|
lm.proxy.on('create', onInit)
|
||||||
|
.on('ready', onReady);
|
||||||
|
};
|
||||||
|
|
||||||
|
Cryptpad.ready(function () {
|
||||||
|
createTodo();
|
||||||
|
Cryptpad.reportAppUsage();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
83
www/oldtodo/todo.js
Normal file
83
www/oldtodo/todo.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
define([
|
||||||
|
|
||||||
|
], function () {
|
||||||
|
var Todo = {};
|
||||||
|
var Cryptpad;
|
||||||
|
|
||||||
|
/* data model
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
"123456789abcdef0",
|
||||||
|
"23456789abcdef01",
|
||||||
|
"0123456789abcedf"
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"0123456789abcedf": {
|
||||||
|
"state": 0, // used to sort completed elements
|
||||||
|
"task": "pewpewpew",
|
||||||
|
"ctime": +new Date(), // used to display chronologically
|
||||||
|
"mtime": +new Date(), // used to display recent actions
|
||||||
|
// "deadline": +new Date() + 1000 * 60 * 60 * 24 * 7
|
||||||
|
},
|
||||||
|
"123456789abcdef0": {},
|
||||||
|
"23456789abcdef01": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
var val = function (proxy, id, k, v) {
|
||||||
|
var el = proxy.data[id];
|
||||||
|
if (!el) {
|
||||||
|
throw new Error('expected an element');
|
||||||
|
}
|
||||||
|
if (typeof(v) === 'function') { el[k] = v(el[k]); }
|
||||||
|
else { el[k] = v; }
|
||||||
|
return el[k];
|
||||||
|
};
|
||||||
|
|
||||||
|
var initialize = function (proxy) {
|
||||||
|
// run migration
|
||||||
|
if (typeof(proxy.data) !== 'object') { proxy.data = {}; }
|
||||||
|
if (!Array.isArray(proxy.order)) { proxy.order = []; }
|
||||||
|
if (typeof(proxy.type) !== 'string') { proxy.type = 'todo'; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/* add (id, obj) push id to order, add object to data */
|
||||||
|
var add = function (proxy, id, obj) {
|
||||||
|
if (!Array.isArray(proxy.order)) {
|
||||||
|
throw new Error('expected an array');
|
||||||
|
}
|
||||||
|
proxy.order.unshift(id);
|
||||||
|
proxy.data[id] = obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* delete (id) remove id from order, delete id from data */
|
||||||
|
var remove = function (proxy, id) {
|
||||||
|
if (Array.isArray(proxy.order)) {
|
||||||
|
var i = proxy.order.indexOf(id);
|
||||||
|
proxy.order.splice(i, 1);
|
||||||
|
}
|
||||||
|
if (proxy.data[id]) { delete proxy.data[id]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
Todo.init = function (proxy, common) {
|
||||||
|
Cryptpad = common;
|
||||||
|
|
||||||
|
var api = {};
|
||||||
|
initialize(proxy);
|
||||||
|
|
||||||
|
api.val = function (id, k, v) {
|
||||||
|
return val(proxy, id, k, v);
|
||||||
|
};
|
||||||
|
api.add = function (id, obj) {
|
||||||
|
return add(proxy, id, obj);
|
||||||
|
};
|
||||||
|
api.remove = function (id) {
|
||||||
|
return remove(proxy, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Todo;
|
||||||
|
});
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
// Defaults to avoid breaking existing themes
|
||||||
|
|
||||||
|
@colortheme_pad-toolbar-bg: #c1e7ff;
|
||||||
|
|
||||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||||
@import (once) '../../customize/src/less2/include/tokenfield.less';
|
@import (once) '../../customize/src/less2/include/tokenfield.less';
|
||||||
@@ -16,7 +20,7 @@
|
|||||||
#cke_1_toolbox {
|
#cke_1_toolbox {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #c1e7ff;
|
background-color: @colortheme_pad-toolbar-bg;
|
||||||
}
|
}
|
||||||
#cke_1_toolbox .cke_toolbar {
|
#cke_1_toolbox .cke_toolbar {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ define([
|
|||||||
'/api/config',
|
'/api/config',
|
||||||
'/common/common-hash.js',
|
'/common/common-hash.js',
|
||||||
'/common/common-util.js',
|
'/common/common-util.js',
|
||||||
|
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||||
|
|
||||||
'/bower_components/diff-dom/diffDOM.js',
|
'/bower_components/diff-dom/diffDOM.js',
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ define([
|
|||||||
MediaTag,
|
MediaTag,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
Hash,
|
Hash,
|
||||||
Util)
|
Util,
|
||||||
|
JsonOT)
|
||||||
{
|
{
|
||||||
var DiffDom = window.diffDOM;
|
var DiffDom = window.diffDOM;
|
||||||
|
|
||||||
@@ -552,7 +554,30 @@ define([
|
|||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
Framework.create({
|
Framework.create({
|
||||||
toolbarContainer: '#cke_1_toolbox',
|
toolbarContainer: '#cke_1_toolbox',
|
||||||
contentContainer: '#cke_1_contents'
|
contentContainer: '#cke_1_contents',
|
||||||
|
transformFunction: JsonOT.validate,
|
||||||
|
thumbnail: {
|
||||||
|
getContainer: function () { return $('iframe').contents().find('html')[0]; },
|
||||||
|
filter: function (el, before) {
|
||||||
|
if (before) {
|
||||||
|
module.cursor.update();
|
||||||
|
$(el).parents().css('overflow', 'visible');
|
||||||
|
$(el).css('max-width', '1200px');
|
||||||
|
$(el).css('max-height', Math.max(600, $(el).width()) + 'px');
|
||||||
|
$(el).css('overflow', 'hidden');
|
||||||
|
$(el).find('body').css('background-color', 'transparent');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(el).parents().css('overflow', '');
|
||||||
|
$(el).css('max-width', '');
|
||||||
|
$(el).css('max-height', '');
|
||||||
|
$(el).css('overflow', '');
|
||||||
|
$(el).find('body').css('background-color', '#fff');
|
||||||
|
var sel = module.cursor.makeSelection();
|
||||||
|
var range = module.cursor.makeRange();
|
||||||
|
module.cursor.fixSelection(sel, range);
|
||||||
|
}
|
||||||
|
}
|
||||||
}, waitFor(function (fw) { window.APP.framework = framework = fw; }));
|
}, waitFor(function (fw) { window.APP.framework = framework = fw; }));
|
||||||
|
|
||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
// Defaults to avoid breaking existing themes
|
||||||
|
|
||||||
|
@colortheme_poll-th-bg: #005bef;
|
||||||
|
@colortheme_poll-th-fg: #fff;
|
||||||
|
@colortheme_poll-help-bg: #bbffbb;
|
||||||
|
|
||||||
@import (once) "../../customize/src/less2/include/browser.less";
|
@import (once) "../../customize/src/less2/include/browser.less";
|
||||||
@import (once) "../../customize/src/less2/include/toolbar.less";
|
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||||
@import (once) "../../customize/src/less2/include/markdown.less";
|
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||||
@@ -14,15 +20,15 @@
|
|||||||
|
|
||||||
@poll-fore: #555;
|
@poll-fore: #555;
|
||||||
|
|
||||||
@poll-th-bg: #005bef;
|
@poll-th-bg: @colortheme_poll-th-bg;
|
||||||
@poll-th-fg: #fff;
|
@poll-th-fg: @colortheme_poll-th-fg;
|
||||||
@poll-th-user-bg: darken(@poll-th-bg, 10%);
|
@poll-th-user-bg: darken(@poll-th-bg, 10%);
|
||||||
@poll-editing: lighten(@poll-th-bg, 10%);
|
@poll-editing: lighten(@poll-th-bg, 10%);
|
||||||
@poll-winner: darken(@poll-th-bg, 15%);
|
@poll-winner: darken(@poll-th-bg, 15%);
|
||||||
@poll-td-bg: @poll-th-bg;
|
@poll-td-bg: @poll-th-bg;
|
||||||
@poll-td-fg: @poll-th-fg;
|
@poll-td-fg: @poll-th-fg;
|
||||||
|
|
||||||
@poll-help-bg: #bbffbb; // lightgreen
|
@poll-help-bg: @colortheme_poll-help-bg;
|
||||||
|
|
||||||
@poll-uncommitted-cell: #eee;
|
@poll-uncommitted-cell: #eee;
|
||||||
@poll-uncommitted-bg: #ddd; //lighten(@poll-th-bg, 50%);
|
@poll-uncommitted-bg: #ddd; //lighten(@poll-th-bg, 50%);
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ define([
|
|||||||
'/poll/render.js',
|
'/poll/render.js',
|
||||||
'/common/diffMarked.js',
|
'/common/diffMarked.js',
|
||||||
'/common/sframe-common-codemirror.js',
|
'/common/sframe-common-codemirror.js',
|
||||||
|
'/common/sframe-common-interface.js',
|
||||||
|
'/common/common-thumbnail.js',
|
||||||
|
|
||||||
'cm/lib/codemirror',
|
'cm/lib/codemirror',
|
||||||
'cm/addon/display/placeholder',
|
'cm/addon/display/placeholder',
|
||||||
'cm/mode/markdown/markdown',
|
'cm/mode/markdown/markdown',
|
||||||
'css!cm/lib/codemirror.css',
|
'css!cm/lib/codemirror.css',
|
||||||
|
|
||||||
|
|
||||||
'/bower_components/file-saver/FileSaver.min.js',
|
'/bower_components/file-saver/FileSaver.min.js',
|
||||||
|
|
||||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
@@ -41,6 +44,8 @@ define([
|
|||||||
Renderer,
|
Renderer,
|
||||||
DiffMd,
|
DiffMd,
|
||||||
SframeCM,
|
SframeCM,
|
||||||
|
SFUI,
|
||||||
|
Thumb,
|
||||||
CMeditor)
|
CMeditor)
|
||||||
{
|
{
|
||||||
var Messages = Cryptpad.Messages;
|
var Messages = Cryptpad.Messages;
|
||||||
@@ -651,7 +656,7 @@ define([
|
|||||||
|
|
||||||
if (editable === false) {
|
if (editable === false) {
|
||||||
// disable all the things
|
// disable all the things
|
||||||
$('.icp-app-poll-realtime input, .cp-app-poll-realtime button, .cp-app-poll-upper button, .cp-app-poll-realtime textarea').attr('disabled', true);
|
$('.cp-app-poll-realtime input, .cp-app-poll-realtime button, .cp-app-poll-upper button, .cp-app-poll-realtime textarea').attr('disabled', true);
|
||||||
$('span.cp-app-poll-table-edit, span.cp-app-poll-table-remove').hide();
|
$('span.cp-app-poll-table-edit, span.cp-app-poll-table-remove').hide();
|
||||||
$('span.cp-app-poll-table-lock').addClass('fa-lock').removeClass('fa-unlock')
|
$('span.cp-app-poll-table-lock').addClass('fa-lock').removeClass('fa-unlock')
|
||||||
.attr('title', Messages.poll_locked)
|
.attr('title', Messages.poll_locked)
|
||||||
@@ -682,6 +687,8 @@ define([
|
|||||||
|
|
||||||
var getCommentId = Render.Uid('c');
|
var getCommentId = Render.Uid('c');
|
||||||
var removeComment = function (uid) {
|
var removeComment = function (uid) {
|
||||||
|
var idx = APP.proxy.commentsOrder.indexOf(uid);
|
||||||
|
if (idx !== -1) { APP.proxy.commentsOrder.splice(idx, 1); }
|
||||||
delete APP.proxy.comments[uid];
|
delete APP.proxy.comments[uid];
|
||||||
APP.updateComments();
|
APP.updateComments();
|
||||||
};
|
};
|
||||||
@@ -691,6 +698,7 @@ define([
|
|||||||
var avatars = {};
|
var avatars = {};
|
||||||
var updateComments = APP.updateComments = function () {
|
var updateComments = APP.updateComments = function () {
|
||||||
if (!APP.proxy.comments) { APP.proxy.comments = {}; }
|
if (!APP.proxy.comments) { APP.proxy.comments = {}; }
|
||||||
|
if (!APP.proxy.commentsOrder) { APP.proxy.commentsOrder = []; }
|
||||||
|
|
||||||
var profile;
|
var profile;
|
||||||
if (common.isLoggedIn()) {
|
if (common.isLoggedIn()) {
|
||||||
@@ -699,10 +707,9 @@ define([
|
|||||||
|
|
||||||
var $comments = APP.$comments.html('');
|
var $comments = APP.$comments.html('');
|
||||||
var comments = APP.proxy.comments;
|
var comments = APP.proxy.comments;
|
||||||
Object.keys(copyObject(comments)).sort(function (a, b) {
|
APP.proxy.commentsOrder.forEach(function (k) {
|
||||||
return comments[a].time > comments[b].time;
|
|
||||||
}).forEach(function (k) {
|
|
||||||
var c = comments[k];
|
var c = comments[k];
|
||||||
|
if (!c) { return; }
|
||||||
var name = c.name || Messages.anonymous;
|
var name = c.name || Messages.anonymous;
|
||||||
var $c = $('<div>', {
|
var $c = $('<div>', {
|
||||||
'class': 'cp-app-poll-comments-list-el'
|
'class': 'cp-app-poll-comments-list-el'
|
||||||
@@ -766,6 +773,7 @@ define([
|
|||||||
};
|
};
|
||||||
var addComment = function () {
|
var addComment = function () {
|
||||||
if (!APP.proxy.comments) { APP.proxy.comments = {}; }
|
if (!APP.proxy.comments) { APP.proxy.comments = {}; }
|
||||||
|
if (!APP.proxy.commentsOrder) { APP.proxy.commentsOrder = []; }
|
||||||
var name = APP.$addComment.find('.cp-app-poll-comments-add-name').val().trim();
|
var name = APP.$addComment.find('.cp-app-poll-comments-add-name').val().trim();
|
||||||
var msg = APP.$addComment.find('.cp-app-poll-comments-add-msg').val().trim();
|
var msg = APP.$addComment.find('.cp-app-poll-comments-add-msg').val().trim();
|
||||||
var time = +new Date();
|
var time = +new Date();
|
||||||
@@ -779,6 +787,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
var uid = getCommentId();
|
var uid = getCommentId();
|
||||||
|
APP.proxy.commentsOrder.push(uid);
|
||||||
APP.proxy.comments[uid] = {
|
APP.proxy.comments[uid] = {
|
||||||
msg: msg,
|
msg: msg,
|
||||||
name: name,
|
name: name,
|
||||||
@@ -790,6 +799,47 @@ define([
|
|||||||
updateComments();
|
updateComments();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var initThumbnails = function () {
|
||||||
|
var privateDat = metadataMgr.getPrivateData();
|
||||||
|
if (!privateDat.thumbnails) { return; } // Thumbnails are disabled
|
||||||
|
var hash = privateDat.availableHashes.editHash ||
|
||||||
|
privateDat.availableHashes.viewHash;
|
||||||
|
if (!hash) { return; }
|
||||||
|
var href = privateDat.pathname + '#' + hash;
|
||||||
|
var $el = $('.cp-app-poll-realtime');
|
||||||
|
//var $el = $('#cp-app-poll-table');
|
||||||
|
var scrollTop;
|
||||||
|
var options = {
|
||||||
|
getContainer: function () { return $el[0]; },
|
||||||
|
filter: function (el, before) {
|
||||||
|
if (before) {
|
||||||
|
$el.parents().css('overflow', 'visible');
|
||||||
|
scrollTop = $('#cp-app-poll-form').scrollTop();
|
||||||
|
$el.css('max-height', Math.max(600, $(el).width()) + 'px');
|
||||||
|
$el.find('tr td:first-child, tr td:last-child, tr td:nth-last-child(2)')
|
||||||
|
.css('position', 'static');
|
||||||
|
$el.find('#cp-app-poll-comments').css('display', 'none');
|
||||||
|
$el.find('#cp-app-poll-table-container').css('text-align', 'center');
|
||||||
|
$el.find('#cp-app-poll-table-scroll').css('margin', 'auto');
|
||||||
|
$el.find('#cp-app-poll-table-scroll').css('max-width', '100%');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$el.parents().css('overflow', '');
|
||||||
|
$el.css('max-height', '');
|
||||||
|
$el.find('#cp-app-poll-comments').css('display', '');
|
||||||
|
$el.find('#cp-app-poll-table-container').css('text-align', '');
|
||||||
|
$el.find('#cp-app-poll-table-scroll').css('margin', '');
|
||||||
|
$el.find('#cp-app-poll-table-scroll').css('max-width', '');
|
||||||
|
$el.find('tr td:first-child, tr td:last-child, tr td:nth-last-child(2)')
|
||||||
|
.css('position', '');
|
||||||
|
$('#cp-app-poll-form').scrollTop(scrollTop);
|
||||||
|
},
|
||||||
|
href: href,
|
||||||
|
getContent: function () { return JSON.stringify(APP.proxy.content); }
|
||||||
|
};
|
||||||
|
Thumb.initPadThumbnails(options);
|
||||||
|
};
|
||||||
|
|
||||||
var checkDeletedCells = function () {
|
var checkDeletedCells = function () {
|
||||||
// faster than forEach?
|
// faster than forEach?
|
||||||
var c;
|
var c;
|
||||||
@@ -810,17 +860,23 @@ define([
|
|||||||
|
|
||||||
if (!isNew) {
|
if (!isNew) {
|
||||||
if (proxy.info) {
|
if (proxy.info) {
|
||||||
// Migration??
|
// Migration
|
||||||
proxy.metadata = proxy.info;
|
proxy.metadata = proxy.info;
|
||||||
delete proxy.info;
|
delete proxy.info;
|
||||||
}
|
}
|
||||||
if (proxy.table) {
|
if (proxy.table) {
|
||||||
// Migration??
|
// Migration
|
||||||
proxy.content = proxy.table;
|
proxy.content = proxy.table;
|
||||||
delete proxy.table;
|
delete proxy.table;
|
||||||
}
|
}
|
||||||
checkDeletedCells();
|
checkDeletedCells();
|
||||||
|
|
||||||
|
if (proxy.comments && !proxy.commentsOrder) { // Migration
|
||||||
|
proxy.commentsOrder = Object.keys(copyObject(proxy.comments)).sort(function (a, b) {
|
||||||
|
return proxy.comments[a].time > proxy.comments[b].time;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (proxy && proxy.metadata) {
|
if (proxy && proxy.metadata) {
|
||||||
metadataMgr.updateMetadata(proxy.metadata);
|
metadataMgr.updateMetadata(proxy.metadata);
|
||||||
}
|
}
|
||||||
@@ -938,6 +994,7 @@ define([
|
|||||||
var $table = APP.$table = $('#cp-app-poll-table-scroll').find('table');
|
var $table = APP.$table = $('#cp-app-poll-table-scroll').find('table');
|
||||||
updateDisplayedTable();
|
updateDisplayedTable();
|
||||||
updateDescription(null, APP.proxy.description || '');
|
updateDescription(null, APP.proxy.description || '');
|
||||||
|
initThumbnails();
|
||||||
|
|
||||||
// Initialize author name for comments.
|
// Initialize author name for comments.
|
||||||
// Disable name modification for logged in users
|
// Disable name modification for logged in users
|
||||||
@@ -1149,6 +1206,17 @@ define([
|
|||||||
mode: "markdown",
|
mode: "markdown",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
APP.$descriptionPublished.click(function (e) {
|
||||||
|
if (!e.target) { return; }
|
||||||
|
var $t = $(e.target);
|
||||||
|
if ($t.is('a') || $t.parents('a').length) {
|
||||||
|
e.preventDefault();
|
||||||
|
var $a = $t.is('a') ? $t : $t.parents('a').first();
|
||||||
|
var href = $a.attr('href');
|
||||||
|
if (!href) { return; }
|
||||||
|
window.open(href);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var listmapConfig = {
|
var listmapConfig = {
|
||||||
data: {},
|
data: {},
|
||||||
|
|||||||
160
www/profile/app-profile.less
Normal file
160
www/profile/app-profile.less
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
@import (once) "../../customize/src/less2/include/browser.less";
|
||||||
|
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||||
|
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||||
|
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||||
|
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||||
|
//@import (once) '../../customize/src/less/mixins.less';
|
||||||
|
//@import (once) '../../customize/src/less/variables.less";
|
||||||
|
|
||||||
|
@import (once) '../../customize/src/less2/include/avatar.less';
|
||||||
|
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
|
||||||
|
|
||||||
|
|
||||||
|
.toolbar_main();
|
||||||
|
.fileupload_main();
|
||||||
|
.alertify_main();
|
||||||
|
.sidebar-layout_main();
|
||||||
|
|
||||||
|
// body
|
||||||
|
&.cp-app-profile {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
#cp-app-profile-header {
|
||||||
|
display: flex;
|
||||||
|
#cp-app-profile-rightside {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#cp-app-profile-avatar {
|
||||||
|
width: 300px;
|
||||||
|
//height: 350px;
|
||||||
|
margin: 10px;
|
||||||
|
margin-right: 20px;
|
||||||
|
text-align: center;
|
||||||
|
&> span {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
height: 300px;
|
||||||
|
width: 300px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
.cp-app-profile-avatar-delete {
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.7;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
media-tag {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
img {
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
height: 40px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#cp-app-profile-displayname, #cp-app-profile-link {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
margin: 10px 0;
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
input:focus ~ .edit {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.cp-app-profile-input-edit {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -25px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.cp-app-profile-input-temp {
|
||||||
|
font-weight: 400;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.cp-app-profile-displayname {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
.cp-app-profile-link {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
.cp-app-profile-displayname, .cp-app-profile-link {
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I tried using flexbox but messed with how the pencil icon was displayed
|
||||||
|
#cp-app-profile-invite-button {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
#cp-app-profile-viewprofile-button {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
#cp-app-profile-description {
|
||||||
|
position: relative;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #DDD;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.cp-app-profile-description-rendered {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
.cp-app-profile-description-ok, .cp-app-profile-description-spin {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
right: 2px;
|
||||||
|
display: none;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
.CodeMirror {
|
||||||
|
border: 1px solid #DDD;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: initial;
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#cp-app-profile-create {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,20 +1,38 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="cp">
|
<html>
|
||||||
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
|
||||||
<head>
|
<head>
|
||||||
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title>
|
<title>CryptPad</title>
|
||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
|
<meta name="referrer" content="no-referrer" />
|
||||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
|
<style>
|
||||||
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css">
|
html, body {
|
||||||
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css">
|
margin: 0px;
|
||||||
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" />
|
padding: 0px;
|
||||||
|
}
|
||||||
|
#sbox-iframe {
|
||||||
|
position:fixed;
|
||||||
|
top:0px;
|
||||||
|
left:0px;
|
||||||
|
bottom:0px;
|
||||||
|
right:0px;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
border:none;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
#sbox-filePicker-iframe {
|
||||||
|
position: fixed;
|
||||||
|
top:0; left:0;
|
||||||
|
bottom:0; right:0;
|
||||||
|
width:100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="html">
|
<body>
|
||||||
<noscript>
|
<iframe id="sbox-iframe">
|
||||||
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
|
||||||
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
|
||||||
</noscript>
|
|
||||||
</html>
|
|
||||||
|
|||||||
18
www/profile/inner.html
Normal file
18
www/profile/inner.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html class="cp-app-noscroll">
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<script async data-bootload="/profile/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
|
<style>
|
||||||
|
.loading-hidden { display: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="cp-app-profile">
|
||||||
|
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||||
|
<div id="cp-sidebarlayout-container"></div>
|
||||||
|
<noscript>
|
||||||
|
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
|
||||||
|
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
|
||||||
|
</noscript>
|
||||||
|
</body>
|
||||||
|
|
||||||
479
www/profile/inner.js
Normal file
479
www/profile/inner.js
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
define([
|
||||||
|
'jquery',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/common/sframe-chainpad-listmap.js',
|
||||||
|
'/common/toolbar3.js',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
'/bower_components/nthen/index.js',
|
||||||
|
'/common/sframe-common.js',
|
||||||
|
'/bower_components/marked/marked.min.js',
|
||||||
|
'cm/lib/codemirror',
|
||||||
|
'cm/mode/markdown/markdown',
|
||||||
|
|
||||||
|
'css!/bower_components/codemirror/lib/codemirror.css',
|
||||||
|
'css!/bower_components/codemirror/addon/dialog/dialog.css',
|
||||||
|
'css!/bower_components/codemirror/addon/fold/foldgutter.css',
|
||||||
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
|
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||||
|
'less!/customize/src/less2/main.less',
|
||||||
|
], function (
|
||||||
|
$,
|
||||||
|
Crypto,
|
||||||
|
Listmap,
|
||||||
|
Toolbar,
|
||||||
|
Cryptpad,
|
||||||
|
nThen,
|
||||||
|
SFCommon,
|
||||||
|
Marked,
|
||||||
|
CodeMirror
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var Messages = Cryptpad.Messages;
|
||||||
|
var APP = window.APP = {
|
||||||
|
Cryptpad: Cryptpad,
|
||||||
|
_onRefresh: []
|
||||||
|
};
|
||||||
|
var onConnectError = function () {
|
||||||
|
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decryption event for avatar mediatag (TODO not needed anymore?)
|
||||||
|
$(window.document).on('decryption', function (e) {
|
||||||
|
var decrypted = e.originalEvent;
|
||||||
|
if (decrypted.callback) { decrypted.callback(); }
|
||||||
|
})
|
||||||
|
.on('decryptionError', function (e) {
|
||||||
|
var error = e.originalEvent;
|
||||||
|
Cryptpad.alert(error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).click(function () {
|
||||||
|
$('.cp-dropdown-content').hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Marked
|
||||||
|
var renderer = new Marked.Renderer();
|
||||||
|
Marked.setOptions({
|
||||||
|
renderer: renderer,
|
||||||
|
sanitize: true
|
||||||
|
});
|
||||||
|
// Tasks list
|
||||||
|
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
||||||
|
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
||||||
|
renderer.listitem = function (text) {
|
||||||
|
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
|
||||||
|
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
|
||||||
|
if (isCheckedTaskItem) {
|
||||||
|
text = text.replace(checkedTaskItemPtn,
|
||||||
|
'<i class="fa fa-check-square" aria-hidden="true"></i> ') + '\n';
|
||||||
|
}
|
||||||
|
if (isUncheckedTaskItem) {
|
||||||
|
text = text.replace(uncheckedTaskItemPtn,
|
||||||
|
'<i class="fa fa-square-o" aria-hidden="true"></i> ') + '\n';
|
||||||
|
}
|
||||||
|
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
|
||||||
|
return '<li'+ cls + '>' + text + '</li>\n';
|
||||||
|
};
|
||||||
|
|
||||||
|
var DISPLAYNAME_ID = "cp-app-profile-displayname";
|
||||||
|
var LINK_ID = "cp-app-profile-link";
|
||||||
|
var AVATAR_ID = "cp-app-profile-avatar";
|
||||||
|
var DESCRIPTION_ID = "cp-app-profile-description";
|
||||||
|
var PUBKEY_ID = "cp-app-profile-pubkey";
|
||||||
|
var CREATE_ID = "cp-app-profile-create";
|
||||||
|
var HEADER_ID = "cp-app-profile-header";
|
||||||
|
var HEADER_RIGHT_ID = "cp-app-profile-rightside";
|
||||||
|
var CREATE_INVITE_BUTTON = 'cp-app-profile-invite-button'; /* jshint ignore: line */
|
||||||
|
var VIEW_PROFILE_BUTTON = 'cp-app-profile-viewprofile-button';
|
||||||
|
|
||||||
|
var common;
|
||||||
|
var sFrameChan;
|
||||||
|
|
||||||
|
var createEditableInput = function ($block, name, ph, getValue, setValue, fallbackValue) {
|
||||||
|
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
|
||||||
|
var lastVal;
|
||||||
|
getValue(function (value) {
|
||||||
|
lastVal = value;
|
||||||
|
var $input = $('<input>', {
|
||||||
|
'id': name+'Input',
|
||||||
|
placeholder: ph
|
||||||
|
}).val(value);
|
||||||
|
var $icon = $('<span>', {'class': 'fa fa-pencil cp-app-profile-input-edit'});
|
||||||
|
var editing = false;
|
||||||
|
var todo = function () {
|
||||||
|
if (editing) { return; }
|
||||||
|
editing = true;
|
||||||
|
|
||||||
|
var newVal = $input.val().trim();
|
||||||
|
|
||||||
|
if (newVal === lastVal) {
|
||||||
|
editing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(newVal, function (err) {
|
||||||
|
if (err) { return void console.error(err); }
|
||||||
|
lastVal = newVal;
|
||||||
|
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
|
||||||
|
editing = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$input.on('keyup', function (e) {
|
||||||
|
if (e.which === 13) { return void todo(); }
|
||||||
|
if (e.which === 27) {
|
||||||
|
$input.val(lastVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$icon.click(function () { $input.focus(); });
|
||||||
|
$input.focus(function () {
|
||||||
|
$input.width('');
|
||||||
|
});
|
||||||
|
$input.focusout(todo);
|
||||||
|
$block.append($input).append($icon);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* jshint ignore:start */
|
||||||
|
var isFriend = function (proxy, edKey) {
|
||||||
|
var friends = Cryptpad.find(proxy, ['friends']);
|
||||||
|
return typeof(edKey) === 'string' && friends && (edKey in friends);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addCreateInviteLinkButton = function ($container) {
|
||||||
|
return;
|
||||||
|
var obj = APP.lm.proxy;
|
||||||
|
|
||||||
|
var proxy = Cryptpad.getProxy();
|
||||||
|
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
|
||||||
|
|
||||||
|
var edKey = obj.edKey;
|
||||||
|
var curveKey = obj.curveKey;
|
||||||
|
|
||||||
|
if (!APP.readOnly || !curveKey || !edKey || userViewHash === window.location.hash.slice(1) || isFriend(proxy, edKey)) {
|
||||||
|
//console.log("edit mode or missing curve key, or you're viewing your own profile");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize user inputs
|
||||||
|
|
||||||
|
var unsafeName = obj.name || '';
|
||||||
|
console.log(unsafeName);
|
||||||
|
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
|
||||||
|
console.log(name);
|
||||||
|
|
||||||
|
console.log("Creating invite button");
|
||||||
|
$("<button>", {
|
||||||
|
id: CREATE_INVITE_BUTTON,
|
||||||
|
title: Messages.profile_inviteButtonTitle,
|
||||||
|
})
|
||||||
|
.addClass('btn btn-success')
|
||||||
|
.text(Messages.profile_inviteButton)
|
||||||
|
.click(function () {
|
||||||
|
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
|
||||||
|
if (!yes) { return; }
|
||||||
|
console.log(obj.curveKey);
|
||||||
|
Cryptpad.alert("TODO");
|
||||||
|
// TODO create a listmap object using your curve keys
|
||||||
|
// TODO fill the listmap object with your invite data
|
||||||
|
// TODO generate link to invite object
|
||||||
|
// TODO copy invite link to clipboard
|
||||||
|
}, null, true);
|
||||||
|
})
|
||||||
|
.appendTo($container);
|
||||||
|
};
|
||||||
|
/* jshint ignore:end */
|
||||||
|
|
||||||
|
var addViewButton = function ($container) {
|
||||||
|
if (APP.readOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash = common.getMetadataMgr().getPrivateData().availableHashes.viewHash;
|
||||||
|
var url = APP.origin + '/profile/#' + hash;
|
||||||
|
|
||||||
|
var $button = $('<button>', {
|
||||||
|
'class': 'btn btn-success',
|
||||||
|
id: VIEW_PROFILE_BUTTON,
|
||||||
|
})
|
||||||
|
.text(Messages.profile_viewMyProfile)
|
||||||
|
.click(function () {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
});
|
||||||
|
$container.append($button);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addDisplayName = function ($container) {
|
||||||
|
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
|
||||||
|
|
||||||
|
|
||||||
|
var getValue = function (cb) {
|
||||||
|
cb(APP.lm.proxy.name);
|
||||||
|
};
|
||||||
|
var placeholder = Messages.profile_namePlaceholder;
|
||||||
|
if (APP.readOnly) {
|
||||||
|
var $span = $('<span>', {'class': DISPLAYNAME_ID}).appendTo($block);
|
||||||
|
getValue(function (value) {
|
||||||
|
$span.text(value || Messages.anonymous);
|
||||||
|
});
|
||||||
|
|
||||||
|
//addCreateInviteLinkButton($block);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var setValue = function (value, cb) {
|
||||||
|
APP.lm.proxy.name = value;
|
||||||
|
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, cb);
|
||||||
|
};
|
||||||
|
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, Messages.anonymous);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addLink = function ($container) {
|
||||||
|
var $block = $('<div>', {id: LINK_ID}).appendTo($container);
|
||||||
|
var getValue = function (cb) {
|
||||||
|
cb(APP.lm.proxy.url);
|
||||||
|
};
|
||||||
|
if (APP.readOnly) {
|
||||||
|
var $a = $('<a>', {
|
||||||
|
'class': LINK_ID,
|
||||||
|
target: '_blank',
|
||||||
|
rel: 'noreferrer noopener'
|
||||||
|
}).appendTo($block);
|
||||||
|
getValue(function (value) {
|
||||||
|
if (!value) {
|
||||||
|
return void $a.hide();
|
||||||
|
}
|
||||||
|
$a.attr('href', value).text(value);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var setValue = function (value, cb) {
|
||||||
|
APP.lm.proxy.url = value;
|
||||||
|
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, cb);
|
||||||
|
};
|
||||||
|
var placeholder = Messages.profile_urlPlaceholder;
|
||||||
|
createEditableInput($block, LINK_ID, placeholder, getValue, setValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addAvatar = function ($container) {
|
||||||
|
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
|
||||||
|
var $span = $('<span>').appendTo($block);
|
||||||
|
var allowedMediaTypes = Cryptpad.avatarAllowedTypes;
|
||||||
|
var sframeChan = common.getSframeChannel();
|
||||||
|
var displayAvatar = function () {
|
||||||
|
$span.html('');
|
||||||
|
if (!APP.lm.proxy.avatar) {
|
||||||
|
$('<img>', {
|
||||||
|
src: '/customize/images/avatar.png',
|
||||||
|
title: Messages.profile_avatar,
|
||||||
|
alt: 'Avatar'
|
||||||
|
}).appendTo($span);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
common.displayAvatar($span, APP.lm.proxy.avatar);
|
||||||
|
|
||||||
|
if (APP.readOnly) { return; }
|
||||||
|
|
||||||
|
var $delButton = $('<button>', {
|
||||||
|
'class': 'cp-app-profile-avatar-delete btn btn-danger fa fa-times',
|
||||||
|
title: Messages.fc_delete
|
||||||
|
});
|
||||||
|
$span.append($delButton);
|
||||||
|
$delButton.click(function () {
|
||||||
|
var old = common.getMetadataMgr().getUserData().avatar;
|
||||||
|
sframeChan.query("Q_PROFILE_AVATAR_REMOVE", old, function (err, err2) {
|
||||||
|
if (err || err2) { return void Cryptpad.log(err || err2); }
|
||||||
|
delete APP.lm.proxy.avatar;
|
||||||
|
displayAvatar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
displayAvatar();
|
||||||
|
if (APP.readOnly) { return; }
|
||||||
|
|
||||||
|
var fmConfig = {
|
||||||
|
noHandlers: true,
|
||||||
|
noStore: true,
|
||||||
|
body: $('body'),
|
||||||
|
onUploaded: function (ev, data) {
|
||||||
|
var old = common.getMetadataMgr().getUserData().avatar;
|
||||||
|
var todo = function () {
|
||||||
|
sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) {
|
||||||
|
if (err || err2) { return void Cryptpad.log(err || err2); }
|
||||||
|
APP.lm.proxy.avatar = data.url;
|
||||||
|
displayAvatar();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (old) {
|
||||||
|
sframeChan.query("Q_PROFILE_AVATAR_REMOVE", old, function (err, err2) {
|
||||||
|
if (err || err2) { return void Cryptpad.log(err || err2); }
|
||||||
|
todo();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
todo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APP.FM = common.createFileManager(fmConfig);
|
||||||
|
var data = {
|
||||||
|
FM: APP.FM,
|
||||||
|
filter: function (file) {
|
||||||
|
var sizeMB = Cryptpad.bytesToMegabytes(file.size);
|
||||||
|
var type = file.type;
|
||||||
|
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1;
|
||||||
|
},
|
||||||
|
accept: ".gif,.jpg,.jpeg,.png"
|
||||||
|
};
|
||||||
|
var $upButton = common.createButton('upload', false, data);
|
||||||
|
$upButton.text(Messages.profile_upload);
|
||||||
|
$upButton.prepend($('<span>', {'class': 'fa fa-upload'}));
|
||||||
|
$block.append($upButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addDescription = function ($container) {
|
||||||
|
var $block = $('<div>', {id: DESCRIPTION_ID}).appendTo($container);
|
||||||
|
|
||||||
|
if (APP.readOnly) {
|
||||||
|
if (!(APP.lm.proxy.description || "").trim()) { return void $block.hide(); }
|
||||||
|
var $div = $('<div>', {'class': 'cp-app-profile-description-rendered'}).appendTo($block);
|
||||||
|
var val = Marked(APP.lm.proxy.description);
|
||||||
|
$div.html(val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('<h3>').text(Messages.profile_description).insertBefore($block);
|
||||||
|
|
||||||
|
var $ok = $('<span>', {
|
||||||
|
'class': 'cp-app-profile-description-ok fa fa-check',
|
||||||
|
title: Messages.saved
|
||||||
|
}).appendTo($block);
|
||||||
|
var $spinner = $('<span>', {
|
||||||
|
'class': 'cp-app-profile-description-spin fa fa-spinner fa-pulse'
|
||||||
|
}).appendTo($block);
|
||||||
|
var $textarea = $('<textarea>').val(APP.lm.proxy.description || '');
|
||||||
|
$block.append($textarea);
|
||||||
|
var editor = APP.editor = CodeMirror.fromTextArea($textarea[0], {
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
styleActiveLine : true,
|
||||||
|
mode: "markdown",
|
||||||
|
});
|
||||||
|
|
||||||
|
var onLocal = function () {
|
||||||
|
$ok.hide();
|
||||||
|
$spinner.show();
|
||||||
|
var val = editor.getValue();
|
||||||
|
APP.lm.proxy.description = val;
|
||||||
|
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
||||||
|
$ok.show();
|
||||||
|
$spinner.hide();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.on('change', onLocal);
|
||||||
|
};
|
||||||
|
|
||||||
|
var addPublicKey = function ($container) {
|
||||||
|
var $block = $('<div>', {id: PUBKEY_ID});
|
||||||
|
$container.append($block);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createLeftside = function () {
|
||||||
|
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'}).appendTo(APP.$leftside);
|
||||||
|
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
|
||||||
|
$category.append($('<span>', {'class': 'fa fa-user'}));
|
||||||
|
$category.addClass('cp-leftside-active');
|
||||||
|
$category.append(Messages.profileButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
var onReady = function () {
|
||||||
|
APP.$container.find('#'+CREATE_ID).remove();
|
||||||
|
|
||||||
|
var obj = APP.lm && APP.lm.proxy;
|
||||||
|
if (!APP.readOnly) {
|
||||||
|
var pubKeys = Cryptpad.getPublicKeys();
|
||||||
|
if (pubKeys && pubKeys.curve) {
|
||||||
|
obj.curveKey = pubKeys.curve;
|
||||||
|
obj.edKey = pubKeys.ed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!APP.initialized) {
|
||||||
|
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
|
||||||
|
addAvatar($header);
|
||||||
|
var $rightside = $('<div>', {id: HEADER_RIGHT_ID}).appendTo($header);
|
||||||
|
addDisplayName($rightside);
|
||||||
|
addLink($rightside);
|
||||||
|
addDescription(APP.$rightside);
|
||||||
|
addViewButton(APP.$rightside);
|
||||||
|
addPublicKey(APP.$rightside);
|
||||||
|
APP.initialized = true;
|
||||||
|
createLeftside();
|
||||||
|
}
|
||||||
|
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
var createToolbar = function () {
|
||||||
|
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
|
||||||
|
var configTb = {
|
||||||
|
displayed: displayed,
|
||||||
|
common: Cryptpad,
|
||||||
|
sfCommon: common,
|
||||||
|
$container: APP.$toolbar,
|
||||||
|
pageTitle: Messages.profileButton,
|
||||||
|
metadataMgr: common.getMetadataMgr(),
|
||||||
|
};
|
||||||
|
APP.toolbar = Toolbar.create(configTb);
|
||||||
|
APP.toolbar.$rightside.hide();
|
||||||
|
};
|
||||||
|
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
$(waitFor(Cryptpad.addLoadingScreen));
|
||||||
|
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
APP.$container = $('#cp-sidebarlayout-container');
|
||||||
|
APP.$toolbar = $('#cp-toolbar');
|
||||||
|
APP.$leftside = $('<div>', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
|
||||||
|
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
|
||||||
|
sFrameChan = common.getSframeChannel();
|
||||||
|
sFrameChan.onReady(waitFor());
|
||||||
|
}).nThen(function (/*waitFor*/) {
|
||||||
|
Cryptpad.onError(function (info) {
|
||||||
|
if (info && info.type === "store") {
|
||||||
|
onConnectError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createToolbar();
|
||||||
|
var metadataMgr = common.getMetadataMgr();
|
||||||
|
var privateData = metadataMgr.getPrivateData();
|
||||||
|
|
||||||
|
APP.origin = privateData.origin;
|
||||||
|
APP.readOnly = privateData.readOnly;
|
||||||
|
|
||||||
|
// If not logged in, you can only view other users's profile
|
||||||
|
if (!privateData.readOnly && !common.isLoggedIn()) {
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
|
||||||
|
var $p = $('<p>', {id: CREATE_ID}).append(Messages.profile_register);
|
||||||
|
var $a = $('<a>', {
|
||||||
|
href: APP.origin + '/register/'
|
||||||
|
});
|
||||||
|
$('<button>', {
|
||||||
|
'class': 'btn btn-success',
|
||||||
|
}).text(Messages.login_register).appendTo($a);
|
||||||
|
$p.append($('<br>')).append($a);
|
||||||
|
APP.$rightside.append($p);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var listmapConfig = {
|
||||||
|
data: {},
|
||||||
|
common: common,
|
||||||
|
userName: 'profile',
|
||||||
|
logLevel: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var lm = APP.lm = Listmap.create(listmapConfig);
|
||||||
|
|
||||||
|
lm.proxy.on('ready', onReady);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,532 +1,95 @@
|
|||||||
require.config({
|
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||||
paths: {
|
|
||||||
cm: '/bower_components/codemirror'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
define([
|
define([
|
||||||
|
'/bower_components/nthen/index.js',
|
||||||
|
'/api/config',
|
||||||
'jquery',
|
'jquery',
|
||||||
'/common/cryptpad-common.js',
|
'/common/requireconfig.js',
|
||||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
'/common/sframe-common-outer.js'
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
|
||||||
'/bower_components/marked/marked.min.js',
|
var requireConfig = RequireConfig();
|
||||||
'/common/toolbar2.js',
|
|
||||||
'cm/lib/codemirror',
|
|
||||||
'cm/mode/markdown/markdown',
|
|
||||||
'less!/profile/main.less',
|
|
||||||
'less!/customize/src/less/toolbar.less',
|
|
||||||
'less!/customize/src/less/cryptpad.less',
|
|
||||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
|
||||||
], function ($, Cryptpad, Listmap, Crypto, Marked, Toolbar, CodeMirror) {
|
|
||||||
|
|
||||||
var APP = window.APP = {
|
// Loaded in load #2
|
||||||
Cryptpad: Cryptpad,
|
nThen(function (waitFor) {
|
||||||
_onRefresh: []
|
$(waitFor());
|
||||||
};
|
}).nThen(function (waitFor) {
|
||||||
|
var req = {
|
||||||
$(window.document).on('decryption', function (e) {
|
cfg: requireConfig,
|
||||||
var decrypted = e.originalEvent;
|
req: [ '/common/loading.js' ],
|
||||||
if (decrypted.callback) { decrypted.callback(); }
|
pfx: window.location.origin
|
||||||
})
|
|
||||||
.on('decryptionError', function (e) {
|
|
||||||
var error = e.originalEvent;
|
|
||||||
Cryptpad.alert(error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Marked
|
|
||||||
var renderer = new Marked.Renderer();
|
|
||||||
Marked.setOptions({
|
|
||||||
renderer: renderer,
|
|
||||||
sanitize: true
|
|
||||||
});
|
|
||||||
// Tasks list
|
|
||||||
var checkedTaskItemPtn = /^\s*\[x\]\s*/;
|
|
||||||
var uncheckedTaskItemPtn = /^\s*\[ \]\s*/;
|
|
||||||
renderer.listitem = function (text) {
|
|
||||||
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
|
|
||||||
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
|
|
||||||
if (isCheckedTaskItem) {
|
|
||||||
text = text.replace(checkedTaskItemPtn,
|
|
||||||
'<i class="fa fa-check-square" aria-hidden="true"></i> ') + '\n';
|
|
||||||
}
|
|
||||||
if (isUncheckedTaskItem) {
|
|
||||||
text = text.replace(uncheckedTaskItemPtn,
|
|
||||||
'<i class="fa fa-square-o" aria-hidden="true"></i> ') + '\n';
|
|
||||||
}
|
|
||||||
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
|
|
||||||
return '<li'+ cls + '>' + text + '</li>\n';
|
|
||||||
};
|
|
||||||
/*renderer.image = function (href, title, text) {
|
|
||||||
if (href.slice(0,6) === '/file/') {
|
|
||||||
var parsed = Cryptpad.parsePadUrl(href);
|
|
||||||
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
|
||||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
|
||||||
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
|
|
||||||
mt += '</media-tag>';
|
|
||||||
return mt;
|
|
||||||
}
|
|
||||||
var out = '<img src="' + href + '" alt="' + text + '"';
|
|
||||||
if (title) {
|
|
||||||
out += ' title="' + title + '"';
|
|
||||||
}
|
|
||||||
out += this.options.xhtml ? '/>' : '>';
|
|
||||||
return out;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
var Messages = Cryptpad.Messages;
|
|
||||||
|
|
||||||
var DISPLAYNAME_ID = "displayName";
|
|
||||||
var LINK_ID = "link";
|
|
||||||
var AVATAR_ID = "avatar";
|
|
||||||
var DESCRIPTION_ID = "description";
|
|
||||||
var PUBKEY_ID = "pubKey";
|
|
||||||
var CREATE_ID = "createProfile";
|
|
||||||
var HEADER_ID = "header";
|
|
||||||
var HEADER_RIGHT_ID = "rightside";
|
|
||||||
var CREATE_INVITE_BUTTON = 'inviteButton'; /* jshint ignore: line */
|
|
||||||
var VIEW_PROFILE_BUTTON = 'viewProfileButton';
|
|
||||||
|
|
||||||
var createEditableInput = function ($block, name, ph, getValue, setValue, realtime, fallbackValue) {
|
|
||||||
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
|
|
||||||
var lastVal;
|
|
||||||
getValue(function (value) {
|
|
||||||
lastVal = value;
|
|
||||||
var $input = $('<input>', {
|
|
||||||
'id': name+'Input',
|
|
||||||
placeholder: ph
|
|
||||||
}).val(value);
|
|
||||||
var $icon = $('<span>', {'class': 'fa fa-pencil edit'});
|
|
||||||
var editing = false;
|
|
||||||
var todo = function () {
|
|
||||||
if (editing) { return; }
|
|
||||||
editing = true;
|
|
||||||
|
|
||||||
var newVal = $input.val().trim();
|
|
||||||
|
|
||||||
if (newVal === lastVal) {
|
|
||||||
editing = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(newVal, function (err) {
|
|
||||||
if (err) { return void console.error(err); }
|
|
||||||
Cryptpad.whenRealtimeSyncs(realtime, function () {
|
|
||||||
lastVal = newVal;
|
|
||||||
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
|
|
||||||
editing = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
$input.on('keyup', function (e) {
|
|
||||||
if (e.which === 13) { return void todo(); }
|
|
||||||
if (e.which === 27) {
|
|
||||||
$input.val(lastVal);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$icon.click(function () { $input.focus(); });
|
|
||||||
$input.focus(function () {
|
|
||||||
$input.width('');
|
|
||||||
});
|
|
||||||
$input.focusout(todo);
|
|
||||||
$block.append($input).append($icon);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* jshint ignore:start */
|
|
||||||
var isFriend = function (proxy, edKey) {
|
|
||||||
var friends = Cryptpad.find(proxy, ['friends']);
|
|
||||||
return typeof(edKey) === 'string' && friends && (edKey in friends);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addCreateInviteLinkButton = function ($container) {
|
|
||||||
return;
|
|
||||||
var obj = APP.lm.proxy;
|
|
||||||
|
|
||||||
var proxy = Cryptpad.getProxy();
|
|
||||||
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
|
|
||||||
|
|
||||||
var edKey = obj.edKey;
|
|
||||||
var curveKey = obj.curveKey;
|
|
||||||
|
|
||||||
if (!APP.readOnly || !curveKey || !edKey || userViewHash === window.location.hash.slice(1) || isFriend(proxy, edKey)) {
|
|
||||||
//console.log("edit mode or missing curve key, or you're viewing your own profile");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitize user inputs
|
|
||||||
|
|
||||||
var unsafeName = obj.name || '';
|
|
||||||
console.log(unsafeName);
|
|
||||||
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
|
|
||||||
console.log(name);
|
|
||||||
|
|
||||||
console.log("Creating invite button");
|
|
||||||
$("<button>", {
|
|
||||||
id: CREATE_INVITE_BUTTON,
|
|
||||||
title: Messages.profile_inviteButtonTitle,
|
|
||||||
})
|
|
||||||
.addClass('btn btn-success')
|
|
||||||
.text(Messages.profile_inviteButton)
|
|
||||||
.click(function () {
|
|
||||||
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
|
|
||||||
if (!yes) { return; }
|
|
||||||
console.log(obj.curveKey);
|
|
||||||
Cryptpad.alert("TODO");
|
|
||||||
// TODO create a listmap object using your curve keys
|
|
||||||
// TODO fill the listmap object with your invite data
|
|
||||||
// TODO generate link to invite object
|
|
||||||
// TODO copy invite link to clipboard
|
|
||||||
}, null, true);
|
|
||||||
})
|
|
||||||
.appendTo($container);
|
|
||||||
};
|
|
||||||
/* jshint ignore:end */
|
|
||||||
|
|
||||||
var addViewButton = function ($container) {
|
|
||||||
if (!Cryptpad.isLoggedIn() || window.location.hash) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash = Cryptpad.find(Cryptpad.getProxy(), ['profile', 'view']);
|
|
||||||
var url = '/profile/#' + hash;
|
|
||||||
|
|
||||||
var $button = $('<button>', {
|
|
||||||
'class': 'btn btn-success',
|
|
||||||
id: VIEW_PROFILE_BUTTON,
|
|
||||||
})
|
|
||||||
.text(Messages.profile_viewMyProfile)
|
|
||||||
.click(function () {
|
|
||||||
window.open(url, '_blank');
|
|
||||||
});
|
|
||||||
$container.append($button);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addDisplayName = function ($container) {
|
|
||||||
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
|
|
||||||
|
|
||||||
|
|
||||||
var getValue = function (cb) {
|
|
||||||
cb(APP.lm.proxy.name);
|
|
||||||
};
|
};
|
||||||
var placeholder = Messages.profile_namePlaceholder;
|
window.rc = requireConfig;
|
||||||
if (APP.readOnly) {
|
window.apiconf = ApiConfig;
|
||||||
var $span = $('<span>', {'class': DISPLAYNAME_ID}).appendTo($block);
|
$('#sbox-iframe').attr('src',
|
||||||
getValue(function (value) {
|
ApiConfig.httpSafeOrigin + '/profile/inner.html?' + requireConfig.urlArgs +
|
||||||
$span.text(value || Messages.anonymous);
|
'#' + encodeURIComponent(JSON.stringify(req)));
|
||||||
});
|
|
||||||
|
|
||||||
//addCreateInviteLinkButton($block);
|
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||||
return;
|
// loading screen setup.
|
||||||
}
|
var done = waitFor();
|
||||||
var setValue = function (value, cb) {
|
var onMsg = function (msg) {
|
||||||
APP.lm.proxy.name = value;
|
var data = JSON.parse(msg.data);
|
||||||
cb();
|
if (data.q !== 'READY') { return; }
|
||||||
|
window.removeEventListener('message', onMsg);
|
||||||
|
var _done = done;
|
||||||
|
done = function () { };
|
||||||
|
_done();
|
||||||
};
|
};
|
||||||
var rt = Cryptpad.getStore().getProxy().info.realtime;
|
window.addEventListener('message', onMsg);
|
||||||
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, rt, Messages.anonymous);
|
}).nThen(function (/*waitFor*/) {
|
||||||
};
|
var getSecrets = function (Cryptpad) {
|
||||||
|
// 1st case: visiting someone else's profile with hash in the URL
|
||||||
var addLink = function ($container) {
|
if (window.location.hash) {
|
||||||
var $block = $('<div>', {id: LINK_ID}).appendTo($container);
|
return Cryptpad.getSecrets('profile', window.location.hash.slice(1));
|
||||||
var getValue = function (cb) {
|
|
||||||
cb(APP.lm.proxy.url);
|
|
||||||
};
|
|
||||||
if (APP.readOnly) {
|
|
||||||
var $a = $('<a>', {
|
|
||||||
'class': LINK_ID,
|
|
||||||
target: '_blank',
|
|
||||||
rel: 'noreferrer noopener'
|
|
||||||
}).appendTo($block);
|
|
||||||
getValue(function (value) {
|
|
||||||
if (!value) {
|
|
||||||
return void $a.hide();
|
|
||||||
}
|
|
||||||
$a.attr('href', value).text(value);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var setValue = function (value, cb) {
|
|
||||||
APP.lm.proxy.url = value;
|
|
||||||
cb();
|
|
||||||
};
|
|
||||||
var rt = APP.lm.realtime;
|
|
||||||
var placeholder = Messages.profile_urlPlaceholder;
|
|
||||||
createEditableInput($block, LINK_ID, placeholder, getValue, setValue, rt);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addAvatar = function ($container) {
|
|
||||||
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
|
|
||||||
var $span = $('<span>').appendTo($block);
|
|
||||||
var allowedMediaTypes = Cryptpad.avatarAllowedTypes;
|
|
||||||
var displayAvatar = function () {
|
|
||||||
$span.html('');
|
|
||||||
if (!APP.lm.proxy.avatar) {
|
|
||||||
$('<img>', {
|
|
||||||
src: '/customize/images/avatar.png',
|
|
||||||
title: Messages.profile_avatar,
|
|
||||||
alt: 'Avatar'
|
|
||||||
}).appendTo($span);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
Cryptpad.displayAvatar($span, APP.lm.proxy.avatar);
|
// 2nd case: visiting our own existing profile
|
||||||
|
var obj = Cryptpad.getProxy();
|
||||||
if (APP.readOnly) { return; }
|
if (obj.profile && obj.profile.view && obj.profile.edit) {
|
||||||
|
return Cryptpad.getSecrets('profile', obj.profile.edit);
|
||||||
var $delButton = $('<button>', {
|
|
||||||
'class': 'delete btn btn-danger fa fa-times',
|
|
||||||
title: Messages.fc_delete
|
|
||||||
});
|
|
||||||
$span.append($delButton);
|
|
||||||
$delButton.click(function () {
|
|
||||||
var oldChanId = Cryptpad.hrefToHexChannelId(APP.lm.proxy.avatar);
|
|
||||||
Cryptpad.unpinPads([oldChanId], function (e) {
|
|
||||||
if (e) { Cryptpad.log(e); }
|
|
||||||
delete APP.lm.proxy.avatar;
|
|
||||||
delete Cryptpad.getProxy().profile.avatar;
|
|
||||||
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
|
||||||
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
|
|
||||||
Cryptpad.whenRealtimeSyncs(driveRt, function () {
|
|
||||||
displayAvatar();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
displayAvatar();
|
|
||||||
if (APP.readOnly) { return; }
|
|
||||||
|
|
||||||
var fmConfig = {
|
|
||||||
noHandlers: true,
|
|
||||||
noStore: true,
|
|
||||||
body: $('body'),
|
|
||||||
onUploaded: function (ev, data) {
|
|
||||||
var chanId = Cryptpad.hrefToHexChannelId(data.url);
|
|
||||||
var profile = Cryptpad.getProxy().profile;
|
|
||||||
var old = profile.avatar;
|
|
||||||
var todo = function () {
|
|
||||||
Cryptpad.pinPads([chanId], function (e) {
|
|
||||||
if (e) { return void Cryptpad.log(e); }
|
|
||||||
APP.lm.proxy.avatar = data.url;
|
|
||||||
Cryptpad.getProxy().profile.avatar = data.url;
|
|
||||||
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
|
||||||
var driveRt = Cryptpad.getStore().getProxy().info.realtime;
|
|
||||||
Cryptpad.whenRealtimeSyncs(driveRt, function () {
|
|
||||||
displayAvatar();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (old) {
|
|
||||||
var oldChanId = Cryptpad.hrefToHexChannelId(old);
|
|
||||||
Cryptpad.unpinPads([oldChanId], function (e) {
|
|
||||||
if (e) { Cryptpad.log(e); }
|
|
||||||
todo();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
todo();
|
|
||||||
}
|
}
|
||||||
};
|
// 3rd case: profile creation (create a new random hash, store it later if needed)
|
||||||
APP.FM = Cryptpad.createFileManager(fmConfig);
|
if (!Cryptpad.isLoggedIn()) { return; }
|
||||||
var data = {
|
var hash = Cryptpad.createRandomHash();
|
||||||
FM: APP.FM,
|
var secret = Cryptpad.getSecrets('profile', hash);
|
||||||
filter: function (file) {
|
Cryptpad.pinPads([secret.channel], function (e) {
|
||||||
var sizeMB = Cryptpad.bytesToMegabytes(file.size);
|
|
||||||
var type = file.type;
|
|
||||||
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1;
|
|
||||||
},
|
|
||||||
accept: ".gif,.jpg,.jpeg,.png"
|
|
||||||
};
|
|
||||||
var $upButton = Cryptpad.createButton('upload', false, data);
|
|
||||||
$upButton.text(Messages.profile_upload);
|
|
||||||
$upButton.prepend($('<span>', {'class': 'fa fa-upload'}));
|
|
||||||
$block.append($upButton);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addDescription = function ($container) {
|
|
||||||
var $block = $('<div>', {id: DESCRIPTION_ID}).appendTo($container);
|
|
||||||
|
|
||||||
if (APP.readOnly) {
|
|
||||||
if (!(APP.lm.proxy.description || "").trim()) { return void $block.hide(); }
|
|
||||||
var $div = $('<div>', {'class': 'rendered'}).appendTo($block);
|
|
||||||
var val = Marked(APP.lm.proxy.description);
|
|
||||||
$div.html(val);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$('<h3>').text(Messages.profile_description).insertBefore($block);
|
|
||||||
|
|
||||||
var $ok = $('<span>', {'class': 'ok fa fa-check', title: Messages.saved}).appendTo($block);
|
|
||||||
var $spinner = $('<span>', {'class': 'spin fa fa-spinner fa-pulse'}).appendTo($block);
|
|
||||||
var $textarea = $('<textarea>').val(APP.lm.proxy.description || '');
|
|
||||||
$block.append($textarea);
|
|
||||||
var editor = APP.editor = CodeMirror.fromTextArea($textarea[0], {
|
|
||||||
lineNumbers: true,
|
|
||||||
lineWrapping: true,
|
|
||||||
styleActiveLine : true,
|
|
||||||
mode: "markdown",
|
|
||||||
});
|
|
||||||
|
|
||||||
var onLocal = function () {
|
|
||||||
$ok.hide();
|
|
||||||
$spinner.show();
|
|
||||||
var val = editor.getValue();
|
|
||||||
APP.lm.proxy.description = val;
|
|
||||||
Cryptpad.whenRealtimeSyncs(APP.lm.realtime, function () {
|
|
||||||
$ok.show();
|
|
||||||
$spinner.hide();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
editor.on('change', onLocal);
|
|
||||||
};
|
|
||||||
|
|
||||||
var addPublicKey = function ($container) {
|
|
||||||
var $block = $('<div>', {id: PUBKEY_ID});
|
|
||||||
$container.append($block);
|
|
||||||
};
|
|
||||||
|
|
||||||
var createLeftside = function () {
|
|
||||||
var $categories = $('<div>', {'class': 'categories'}).appendTo(APP.$leftside);
|
|
||||||
APP.$usage = $('<div>', {'class': 'usage'}).appendTo(APP.$leftside);
|
|
||||||
|
|
||||||
var $category = $('<div>', {'class': 'category'}).appendTo($categories);
|
|
||||||
$category.append($('<span>', {'class': 'fa fa-user'}));
|
|
||||||
$category.addClass('active');
|
|
||||||
$category.append(Messages.profileButton);
|
|
||||||
};
|
|
||||||
|
|
||||||
var createToolbar = function () {
|
|
||||||
var displayed = ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'];
|
|
||||||
var configTb = {
|
|
||||||
displayed: displayed,
|
|
||||||
ifrw: window,
|
|
||||||
common: Cryptpad,
|
|
||||||
$container: APP.$toolbar,
|
|
||||||
pageTitle: Messages.profileButton
|
|
||||||
};
|
|
||||||
var toolbar = APP.toolbar = Toolbar.create(configTb);
|
|
||||||
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
|
|
||||||
};
|
|
||||||
|
|
||||||
var onReady = function () {
|
|
||||||
APP.$container.find('#'+CREATE_ID).remove();
|
|
||||||
|
|
||||||
var obj = APP.lm && APP.lm.proxy;
|
|
||||||
if (!APP.readOnly) {
|
|
||||||
var pubKeys = Cryptpad.getPublicKeys();
|
|
||||||
if (pubKeys && pubKeys.curve) {
|
|
||||||
obj.curveKey = pubKeys.curve;
|
|
||||||
obj.edKey = pubKeys.ed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!APP.initialized) {
|
|
||||||
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
|
|
||||||
addAvatar($header);
|
|
||||||
var $rightside = $('<div>', {id: HEADER_RIGHT_ID}).appendTo($header);
|
|
||||||
addDisplayName($rightside);
|
|
||||||
addLink($rightside);
|
|
||||||
addDescription(APP.$rightside);
|
|
||||||
addViewButton(APP.$rightside); //$rightside);
|
|
||||||
addPublicKey(APP.$rightside);
|
|
||||||
APP.initialized = true;
|
|
||||||
createLeftside();
|
|
||||||
}
|
|
||||||
|
|
||||||
Cryptpad.removeLoadingScreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
var onInit = function () {
|
|
||||||
|
|
||||||
};
|
|
||||||
var onDisconnect = function () {};
|
|
||||||
var onChange = function () {};
|
|
||||||
|
|
||||||
var andThen = function (profileHash) {
|
|
||||||
var secret = Cryptpad.getSecrets('profile', profileHash);
|
|
||||||
var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr;
|
|
||||||
var listmapConfig = {
|
|
||||||
data: {},
|
|
||||||
websocketURL: Cryptpad.getWebsocketURL(),
|
|
||||||
channel: secret.channel,
|
|
||||||
readOnly: readOnly,
|
|
||||||
validateKey: secret.keys.validateKey || undefined,
|
|
||||||
crypto: Crypto.createEncryptor(secret.keys),
|
|
||||||
userName: 'profile',
|
|
||||||
logLevel: 1,
|
|
||||||
};
|
|
||||||
var lm = APP.lm = Listmap.create(listmapConfig);
|
|
||||||
lm.proxy.on('create', onInit)
|
|
||||||
.on('ready', onReady)
|
|
||||||
.on('disconnect', onDisconnect)
|
|
||||||
.on('change', [], onChange);
|
|
||||||
};
|
|
||||||
|
|
||||||
var getOrCreateProfile = function () {
|
|
||||||
var obj = Cryptpad.getStore().getProxy().proxy;
|
|
||||||
if (obj.profile && obj.profile.view && obj.profile.edit) {
|
|
||||||
return void andThen(obj.profile.edit);
|
|
||||||
}
|
|
||||||
// If the user doesn't have a public profile, ask them if they want to create one
|
|
||||||
var todo = function () {
|
|
||||||
var secret = Cryptpad.getSecrets();
|
|
||||||
obj.profile = {};
|
|
||||||
var channel = Cryptpad.createChannelId();
|
|
||||||
Cryptpad.pinPads([channel], function (e) {
|
|
||||||
if (e) {
|
if (e) {
|
||||||
if (e === 'E_OVER_LIMIT') {
|
if (e === 'E_OVER_LIMIT') {
|
||||||
Cryptpad.alert(Messages.pinLimitNotPinned, null, true);
|
// TODO
|
||||||
}
|
}
|
||||||
return void Cryptpad.log(Messages._getKey('profile_error', [e]));
|
return;
|
||||||
|
//return void Cryptpad.log(Messages._getKey('profile_error', [e])) // TODO
|
||||||
}
|
}
|
||||||
obj.profile.edit = Cryptpad.getEditHashFromKeys(channel, secret.keys);
|
obj.profile = {};
|
||||||
obj.profile.view = Cryptpad.getViewHashFromKeys(channel, secret.keys);
|
obj.profile.edit = Cryptpad.getEditHashFromKeys(secret.channel, secret.keys);
|
||||||
andThen(obj.profile.edit);
|
obj.profile.view = Cryptpad.getViewHashFromKeys(secret.channel, secret.keys);
|
||||||
|
});
|
||||||
|
return secret;
|
||||||
|
};
|
||||||
|
var addRpc = function (sframeChan, Cryptpad) {
|
||||||
|
// Adding a new avatar from the profile: pin it and store it in the object
|
||||||
|
sframeChan.on('Q_PROFILE_AVATAR_ADD', function (data, cb) {
|
||||||
|
var chanId = Cryptpad.hrefToHexChannelId(data);
|
||||||
|
Cryptpad.pinPads([chanId], function (e) {
|
||||||
|
if (e) { return void cb(e); }
|
||||||
|
Cryptpad.getProxy().profile.avatar = data;
|
||||||
|
Cryptpad.whenRealtimeSyncs(Cryptpad.getRealtime(), function () {
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// Removing the avatar from the profile: unpin it
|
||||||
|
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
|
||||||
|
var chanId = Cryptpad.hrefToHexChannelId(data);
|
||||||
|
Cryptpad.unpinPads([chanId], function (e) {
|
||||||
|
delete Cryptpad.getProxy().profile.avatar;
|
||||||
|
cb(e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
SFCommonO.start({
|
||||||
Cryptpad.removeLoadingScreen();
|
getSecrets: getSecrets,
|
||||||
|
noHash: true, // Don't add the hash in the URL if it doesn't already exist
|
||||||
if (!Cryptpad.isLoggedIn()) {
|
addRpc: addRpc,
|
||||||
var $p = $('<p>', {id: CREATE_ID}).append(Messages.profile_register);
|
noRealtime: !localStorage.User_hash
|
||||||
var $a = $('<a>', {
|
|
||||||
href: '/register/'
|
|
||||||
});
|
|
||||||
$('<button>', {
|
|
||||||
'class': 'btn btn-success',
|
|
||||||
}).text(Messages.login_register).appendTo($a);
|
|
||||||
$p.append($('<br>')).append($a);
|
|
||||||
APP.$rightside.append($p);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make an empty profile for the user on their first visit
|
|
||||||
todo();
|
|
||||||
};
|
|
||||||
|
|
||||||
var onCryptpadReady = function () {
|
|
||||||
APP.$leftside = $('<div>', {id: 'leftSide'}).appendTo(APP.$container);
|
|
||||||
APP.$rightside = $('<div>', {id: 'rightSide'}).appendTo(APP.$container);
|
|
||||||
|
|
||||||
createToolbar();
|
|
||||||
|
|
||||||
if (window.location.hash) {
|
|
||||||
return void andThen(window.location.hash.slice(1));
|
|
||||||
}
|
|
||||||
getOrCreateProfile();
|
|
||||||
};
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
$(window).click(function () {
|
|
||||||
$('.cp-dropdown-content').hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
APP.$container = $('#container');
|
|
||||||
APP.$toolbar = $('#toolbar');
|
|
||||||
|
|
||||||
Cryptpad.ready(function () {
|
|
||||||
Cryptpad.reportAppUsage();
|
|
||||||
onCryptpadReady();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ define([
|
|||||||
'languageSelector',
|
'languageSelector',
|
||||||
'logoutEverywhere',
|
'logoutEverywhere',
|
||||||
'resetTips',
|
'resetTips',
|
||||||
|
'thumbnails',
|
||||||
'userFeedback'
|
'userFeedback'
|
||||||
],
|
],
|
||||||
'drive': [
|
'drive': [
|
||||||
@@ -220,6 +221,60 @@ define([
|
|||||||
|
|
||||||
return $div;
|
return $div;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var createThumbnails = function (obj) {
|
||||||
|
var $div = $('<div>', {'class': 'thumbnails element'});
|
||||||
|
$('<label>').text(Messages.settings_thumbnails).appendTo($div);
|
||||||
|
|
||||||
|
// Disable
|
||||||
|
$('<span>', {'class': 'description'})
|
||||||
|
.text(Messages.settings_disableThumbnailsDescription).appendTo($div);
|
||||||
|
var $label = $('<label>', { 'for': 'disableThumbnails', 'class': 'noTitle' })
|
||||||
|
.text(Messages.settings_disableThumbnailsAction);
|
||||||
|
|
||||||
|
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
|
||||||
|
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
|
||||||
|
|
||||||
|
var $checkbox = $('<input>', {
|
||||||
|
'type': 'checkbox',
|
||||||
|
id: 'disableThumbnails'
|
||||||
|
}).on('change', function () {
|
||||||
|
$spinner.show();
|
||||||
|
$ok.hide();
|
||||||
|
if (!obj.proxy.settings) { obj.proxy.settings = {}; }
|
||||||
|
if (!obj.proxy.settings.general) { obj.proxy.settings.general = {}; }
|
||||||
|
obj.proxy.settings.general.disableThumbnails = $checkbox.is(':checked') || false;
|
||||||
|
Cryptpad.whenRealtimeSyncs(obj.info.realtime, function () {
|
||||||
|
$spinner.hide();
|
||||||
|
$ok.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$checkbox.appendTo($div);
|
||||||
|
$label.appendTo($div);
|
||||||
|
|
||||||
|
$ok.hide().appendTo($div);
|
||||||
|
$spinner.hide().appendTo($div);
|
||||||
|
|
||||||
|
if (obj.proxy.settings && obj.proxy.settings.general
|
||||||
|
&& obj.proxy.settings.general.disableThumbnails) {
|
||||||
|
$checkbox[0].checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
/*$('<span>', {'class': 'description'})
|
||||||
|
.text(Messages.settings_resetThumbnailsDescription).appendTo($div);
|
||||||
|
var $button = $('<button>', {'id': 'resetThumbnails', 'class': 'btn btn-primary'})
|
||||||
|
.text(Messages.settings_resetThumbnailsAction).appendTo($div);
|
||||||
|
|
||||||
|
$button.click(function () {
|
||||||
|
// TODO we need to have access to the sandbox localforage first...
|
||||||
|
Cryptpad.alert(Messages.settings_resetThumbnailsDone);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
return $div;
|
||||||
|
};
|
||||||
|
|
||||||
var createBackupDrive = function (store) {
|
var createBackupDrive = function (store) {
|
||||||
var obj = store.proxy;
|
var obj = store.proxy;
|
||||||
var $div = $('<div>', {'class': 'backupDrive element'});
|
var $div = $('<div>', {'class': 'backupDrive element'});
|
||||||
@@ -467,6 +522,7 @@ define([
|
|||||||
$rightside.append(createLogoutEverywhere(obj));
|
$rightside.append(createLogoutEverywhere(obj));
|
||||||
}
|
}
|
||||||
$rightside.append(createResetTips());
|
$rightside.append(createResetTips());
|
||||||
|
$rightside.append(createThumbnails(obj));
|
||||||
$rightside.append(createBackupDrive(obj));
|
$rightside.append(createBackupDrive(obj));
|
||||||
$rightside.append(createImportLocalPads(obj));
|
$rightside.append(createImportLocalPads(obj));
|
||||||
$rightside.append(createResetDrive(obj));
|
$rightside.append(createResetDrive(obj));
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
.cp {
|
.cp {
|
||||||
#container {
|
#container {
|
||||||
#rightSide {
|
#rightSide {
|
||||||
.userFeedback {
|
.userFeedback, .thumbnails {
|
||||||
input {
|
input[type="checkbox"] {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,7 +402,6 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
framework.setContentGetter(function () {
|
framework.setContentGetter(function () {
|
||||||
console.log(framework._.title.title);
|
|
||||||
var content = CodeMirror.getContent();
|
var content = CodeMirror.getContent();
|
||||||
Slide.update(content.content);
|
Slide.update(content.content);
|
||||||
return content;
|
return content;
|
||||||
@@ -467,6 +466,17 @@ define([
|
|||||||
framework.start();
|
framework.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var getThumbnailContainer = function () {
|
||||||
|
var $codeMirror = $('.CodeMirror');
|
||||||
|
var $c = $('#cp-app-slide-editor');
|
||||||
|
if ($c.hasClass('cp-app-slide-preview')) {
|
||||||
|
return $('.cp-app-slide-frame').first()[0];
|
||||||
|
}
|
||||||
|
if ($codeMirror.length) {
|
||||||
|
return $codeMirror[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var main = function () {
|
var main = function () {
|
||||||
var CodeMirror;
|
var CodeMirror;
|
||||||
var editor;
|
var editor;
|
||||||
@@ -474,10 +484,21 @@ define([
|
|||||||
var framework;
|
var framework;
|
||||||
|
|
||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
|
|
||||||
Framework.create({
|
Framework.create({
|
||||||
toolbarContainer: '#cme_toolbox',
|
toolbarContainer: '#cme_toolbox',
|
||||||
contentContainer: '#cp-app-slide-editor'
|
contentContainer: '#cp-app-slide-editor',
|
||||||
|
thumbnail: {
|
||||||
|
getContainer: getThumbnailContainer,
|
||||||
|
filter: function (el, before) {
|
||||||
|
var metadataMgr = framework._.cpNfInner.metadataMgr;
|
||||||
|
var metadata = metadataMgr.getMetadata();
|
||||||
|
if (before) {
|
||||||
|
$(el).css('background-color', metadata.backColor || '#000');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$(el).css('background-color', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
}, waitFor(function (fw) { framework = fw; }));
|
}, waitFor(function (fw) { framework = fw; }));
|
||||||
|
|
||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
|
|||||||
130
www/todo/app-todo.less
Normal file
130
www/todo/app-todo.less
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
@import (once) "../../customize/src/less2/include/browser.less";
|
||||||
|
@import (once) "../../customize/src/less2/include/toolbar.less";
|
||||||
|
@import (once) "../../customize/src/less2/include/markdown.less";
|
||||||
|
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||||
|
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||||
|
//@import (once) '../../customize/src/less/mixins.less';
|
||||||
|
//@import (once) '../../customize/src/less/variables.less";
|
||||||
|
|
||||||
|
@import (once) '../../customize/src/less2/include/avatar.less';
|
||||||
|
|
||||||
|
|
||||||
|
.toolbar_main();
|
||||||
|
.fileupload_main();
|
||||||
|
.alertify_main();
|
||||||
|
|
||||||
|
// body
|
||||||
|
&.cp-app-todo {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
|
||||||
|
@button-border: 2px;
|
||||||
|
|
||||||
|
#cp-toolbar {
|
||||||
|
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-cryptpad-toolbar {
|
||||||
|
padding: 0px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cp-app-todo-container {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-flow: column;
|
||||||
|
padding: 20px;
|
||||||
|
align-items: center;
|
||||||
|
background-color: lighten(@colortheme_todo-bg, 15%);
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@spacing: 15px;
|
||||||
|
|
||||||
|
#cp-app-todo-taskslist {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
min-width: 40%;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-app-todo-create-form {
|
||||||
|
margin: @spacing;
|
||||||
|
min-width: 40%;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
#cp-app-todo-newtodo {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 15px;
|
||||||
|
border-radius: 0;
|
||||||
|
border: 0;
|
||||||
|
background-color: darken(@colortheme_todo-bg, 10%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 5px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: darken(@colortheme_todo-bg, 20%);
|
||||||
|
border:0;
|
||||||
|
&:hover {
|
||||||
|
background-color: darken(@colortheme_todo-bg, 25%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-app-todo-task {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: @spacing;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
&.cp-app-todo-task-complete {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cp-app-todo-task-text {
|
||||||
|
margin: @spacing;
|
||||||
|
flex: 1;
|
||||||
|
word-wrap: break-word;
|
||||||
|
min-width: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.cp-app-todo-task-date {
|
||||||
|
margin: @spacing;
|
||||||
|
}
|
||||||
|
.cp-app-todo-task-remove {
|
||||||
|
margin: @spacing;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.cp-app-todo-task-checkbox {
|
||||||
|
font-size: 45px;
|
||||||
|
width: 45px;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.cp-app-todo-task-checkbox-checked {
|
||||||
|
|
||||||
|
}
|
||||||
|
.cp-app-todo-task-checkbox-unchecked {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
border:0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="cp pad">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>CryptPad</title>
|
<title>CryptPad</title>
|
||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
<meta name="referrer" content="no-referrer" />
|
||||||
|
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
#pad-iframe {
|
#sbox-iframe {
|
||||||
position:fixed;
|
position:fixed;
|
||||||
top:0px;
|
top:0px;
|
||||||
left:0px;
|
left:0px;
|
||||||
@@ -23,8 +24,15 @@
|
|||||||
padding:0;
|
padding:0;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
}
|
}
|
||||||
|
#sbox-filePicker-iframe {
|
||||||
|
position: fixed;
|
||||||
|
top:0; left:0;
|
||||||
|
bottom:0; right:0;
|
||||||
|
width:100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
<iframe id="sbox-iframe">
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html class="cp-app-noscroll">
|
||||||
<head>
|
<head>
|
||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
<script async data-bootload="/todo/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||||
<script async data-bootload="/todo/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
<style>
|
||||||
<style>.loading-hidden, .loading-hidden * {display: none !important;}</style>
|
.loading-hidden { display: none; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="loading-hidden">
|
<body class="cp-app-todo">
|
||||||
<div id="toolbar" class="toolbar-container"></div>
|
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||||
<div id="container">
|
<div id="cp-app-todo-container">
|
||||||
<div class="cp-create-form">
|
<div class="cp-app-todo-create-form">
|
||||||
<input type="text" id="newTodoName" data-localization-placeholder="todo_newTodoNamePlaceholder" />
|
<input type="text" id="cp-app-todo-newtodo" data-localization-placeholder="todo_newTodoNamePlaceholder" />
|
||||||
<button class="btn btn-success fa fa-plus" data-localization-title="todo_newTodoNameTitle"></button>
|
<button class="btn btn-success fa fa-plus" data-localization-title="todo_newTodoNameTitle"></button>
|
||||||
</div>
|
</div>
|
||||||
<div id="tasksList"></div>
|
<div id="cp-app-todo-taskslist"></div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,229 @@
|
|||||||
define([
|
define([
|
||||||
'jquery',
|
'jquery',
|
||||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/common/sframe-chainpad-listmap.js',
|
||||||
|
'/common/toolbar3.js',
|
||||||
|
'/common/cryptpad-common.js',
|
||||||
|
'/bower_components/nthen/index.js',
|
||||||
|
'/common/sframe-common.js',
|
||||||
|
'/todo/todo.js',
|
||||||
|
|
||||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||||
'less!/todo/todo.less',
|
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||||
//'less!/customize/src/less/cryptpad.less',
|
'less!/customize/src/less2/main.less',
|
||||||
'less!/customize/src/less/toolbar.less',
|
], function (
|
||||||
], function ($) {
|
$,
|
||||||
$('.loading-hidden').removeClass('loading-hidden');
|
Crypto,
|
||||||
// dirty hack to get rid the flash of the lock background
|
Listmap,
|
||||||
/*
|
Toolbar,
|
||||||
setTimeout(function () {
|
Cryptpad,
|
||||||
$('#app').addClass('ready');
|
nThen,
|
||||||
}, 100);*/
|
SFCommon,
|
||||||
|
Todo
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var Messages = Cryptpad.Messages;
|
||||||
|
var APP = window.APP = {};
|
||||||
|
var onConnectError = function () {
|
||||||
|
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||||
|
};
|
||||||
|
|
||||||
|
var common;
|
||||||
|
var sFrameChan;
|
||||||
|
nThen(function (waitFor) {
|
||||||
|
$(waitFor(Cryptpad.addLoadingScreen));
|
||||||
|
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
sFrameChan = common.getSframeChannel();
|
||||||
|
sFrameChan.onReady(waitFor());
|
||||||
|
}).nThen(function (/*waitFor*/) {
|
||||||
|
Cryptpad.onError(function (info) {
|
||||||
|
if (info && info.type === "store") {
|
||||||
|
onConnectError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var $body = $('body');
|
||||||
|
var $list = $('#cp-app-todo-taskslist');
|
||||||
|
|
||||||
|
var removeTips = function () {
|
||||||
|
Cryptpad.clearTooltips();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onReady = function () {
|
||||||
|
var todo = Todo.init(APP.lm.proxy);
|
||||||
|
|
||||||
|
var deleteTask = function(id) {
|
||||||
|
todo.remove(id);
|
||||||
|
|
||||||
|
var $els = $list.find('.cp-app-todo-task').filter(function (i, el) {
|
||||||
|
return $(el).data('id') === id;
|
||||||
|
});
|
||||||
|
$els.fadeOut(null, function () {
|
||||||
|
$els.remove();
|
||||||
|
removeTips();
|
||||||
|
});
|
||||||
|
//APP.display();
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO make this actually work, and scroll to bottom...
|
||||||
|
var scrollTo = function (t) {
|
||||||
|
$list.animate({
|
||||||
|
scrollTop: t,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
scrollTo = scrollTo;
|
||||||
|
|
||||||
|
var makeCheckbox = function (id, cb) {
|
||||||
|
var entry = APP.lm.proxy.data[id];
|
||||||
|
var checked = entry.state === 1 ?
|
||||||
|
'cp-app-todo-task-checkbox-checked fa-check-square-o':
|
||||||
|
'cp-app-todo-task-checkbox-unchecked fa-square-o';
|
||||||
|
|
||||||
|
var title = entry.state === 1?
|
||||||
|
Messages.todo_markAsIncompleteTitle:
|
||||||
|
Messages.todo_markAsCompleteTitle;
|
||||||
|
title = title;
|
||||||
|
|
||||||
|
removeTips();
|
||||||
|
return $('<span>', {
|
||||||
|
'class': 'cp-app-todo-task-checkbox fa ' + checked,
|
||||||
|
//title: title,
|
||||||
|
}).on('click', function () {
|
||||||
|
entry.state = (entry.state + 1) % 2;
|
||||||
|
if (typeof(cb) === 'function') {
|
||||||
|
cb(entry.state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var addTaskUI = function (el, animate) {
|
||||||
|
var $taskDiv = $('<div>', {
|
||||||
|
'class': 'cp-app-todo-task'
|
||||||
|
});
|
||||||
|
if (animate) {
|
||||||
|
$taskDiv.prependTo($list);
|
||||||
|
} else {
|
||||||
|
$taskDiv.appendTo($list);
|
||||||
|
}
|
||||||
|
$taskDiv.data('id', el);
|
||||||
|
|
||||||
|
makeCheckbox(el, function (/*state*/) {
|
||||||
|
APP.display();
|
||||||
|
})
|
||||||
|
.appendTo($taskDiv);
|
||||||
|
|
||||||
|
var entry = APP.lm.proxy.data[el];
|
||||||
|
|
||||||
|
if (entry.state) {
|
||||||
|
$taskDiv.addClass('cp-app-todo-task-complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('<span>', { 'class': 'cp-app-todo-task-text' })
|
||||||
|
.text(entry.task)
|
||||||
|
.appendTo($taskDiv);
|
||||||
|
/*$('<span>', { 'class': 'cp-app-todo-task-date' })
|
||||||
|
.text(new Date(entry.ctime).toLocaleString())
|
||||||
|
.appendTo($taskDiv);*/
|
||||||
|
$('<button>', {
|
||||||
|
'class': 'fa fa-times cp-app-todo-task-remove btn btn-danger',
|
||||||
|
title: Messages.todo_removeTaskTitle,
|
||||||
|
}).appendTo($taskDiv).on('click', function() {
|
||||||
|
deleteTask(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
$taskDiv.hide();
|
||||||
|
window.setTimeout(function () {
|
||||||
|
// ???
|
||||||
|
$taskDiv.fadeIn();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
removeTips();
|
||||||
|
};
|
||||||
|
var display = APP.display = function () {
|
||||||
|
$list.empty();
|
||||||
|
removeTips();
|
||||||
|
APP.lm.proxy.order.forEach(function (el) {
|
||||||
|
addTaskUI(el);
|
||||||
|
});
|
||||||
|
//scrollTo('300px');
|
||||||
|
};
|
||||||
|
|
||||||
|
var addTask = function () {
|
||||||
|
var $input = $('#cp-app-todo-newtodo');
|
||||||
|
// if the input is empty after removing leading and trailing spaces
|
||||||
|
// don't create a new entry
|
||||||
|
if (!$input.val().trim()) { return; }
|
||||||
|
|
||||||
|
var obj = {
|
||||||
|
"state": 0,
|
||||||
|
"task": $input.val(),
|
||||||
|
"ctime": +new Date(),
|
||||||
|
"mtime": +new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
var id = Cryptpad.createChannelId();
|
||||||
|
todo.add(id, obj);
|
||||||
|
|
||||||
|
$input.val("");
|
||||||
|
addTaskUI(id, true);
|
||||||
|
//display();
|
||||||
|
};
|
||||||
|
|
||||||
|
var $formSubmit = $('.cp-app-todo-create-form button').on('click', addTask);
|
||||||
|
$('#cp-app-todo-newtodo').on('keypress', function (e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case 13:
|
||||||
|
$formSubmit.click();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//console.log(e.which);
|
||||||
|
}
|
||||||
|
}).focus();
|
||||||
|
|
||||||
|
var editTask = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
editTask = editTask;
|
||||||
|
|
||||||
|
display();
|
||||||
|
Cryptpad.removeLoadingScreen();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onInit = function () {
|
||||||
|
Cryptpad.addLoadingScreen();
|
||||||
|
|
||||||
|
$body.on('dragover', function (e) { e.preventDefault(); });
|
||||||
|
$body.on('drop', function (e) { e.preventDefault(); });
|
||||||
|
|
||||||
|
var $bar = $('.cp-toolbar-container');
|
||||||
|
|
||||||
|
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle'];
|
||||||
|
var configTb = {
|
||||||
|
displayed: displayed,
|
||||||
|
common: Cryptpad,
|
||||||
|
sfCommon: common,
|
||||||
|
$container: $bar,
|
||||||
|
pageTitle: Messages.todo_title,
|
||||||
|
metadataMgr: common.getMetadataMgr(),
|
||||||
|
};
|
||||||
|
APP.toolbar = Toolbar.create(configTb);
|
||||||
|
APP.toolbar.$rightside.hide();
|
||||||
|
};
|
||||||
|
var createTodo = function() {
|
||||||
|
var listmapConfig = {
|
||||||
|
data: {},
|
||||||
|
common: common,
|
||||||
|
userName: 'todo',
|
||||||
|
logLevel: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
var lm = APP.lm = Listmap.create(listmapConfig);
|
||||||
|
|
||||||
|
lm.proxy.on('create', onInit)
|
||||||
|
.on('ready', onReady);
|
||||||
|
};
|
||||||
|
createTodo();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
260
www/todo/main.js
260
www/todo/main.js
@@ -1,229 +1,49 @@
|
|||||||
|
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||||
define([
|
define([
|
||||||
|
'/bower_components/nthen/index.js',
|
||||||
|
'/api/config',
|
||||||
'jquery',
|
'jquery',
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
'/common/requireconfig.js',
|
||||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
'/common/sframe-common-outer.js'
|
||||||
'/common/toolbar2.js',
|
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
|
||||||
'/common/cryptpad-common.js',
|
var requireConfig = RequireConfig();
|
||||||
'/todo/todo.js',
|
|
||||||
|
|
||||||
//'/common/media-tag.js',
|
// Loaded in load #2
|
||||||
//'/bower_components/file-saver/FileSaver.min.js',
|
nThen(function (waitFor) {
|
||||||
|
$(waitFor());
|
||||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
}).nThen(function (waitFor) {
|
||||||
'less!/customize/src/less/cryptpad.less',
|
var req = {
|
||||||
], function ($, Crypto, Listmap, Toolbar, Cryptpad, Todo) {
|
cfg: requireConfig,
|
||||||
var Messages = Cryptpad.Messages;
|
req: [ '/common/loading.js' ],
|
||||||
|
pfx: window.location.origin
|
||||||
var APP = window.APP = {};
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
var $iframe = $('#pad-iframe').contents();
|
|
||||||
var $body = $iframe.find('body');
|
|
||||||
var ifrw = $('#pad-iframe')[0].contentWindow;
|
|
||||||
var $list = $iframe.find('#tasksList');
|
|
||||||
|
|
||||||
var removeTips = function () {
|
|
||||||
Cryptpad.clearTooltips();
|
|
||||||
};
|
|
||||||
|
|
||||||
var onReady = function () {
|
|
||||||
|
|
||||||
var todo = Todo.init(APP.lm.proxy, Cryptpad);
|
|
||||||
|
|
||||||
var deleteTask = function(id) {
|
|
||||||
todo.remove(id);
|
|
||||||
|
|
||||||
var $els = $list.find('.cp-task').filter(function (i, el) {
|
|
||||||
return $(el).data('id') === id;
|
|
||||||
});
|
|
||||||
$els.fadeOut(null, function () {
|
|
||||||
$els.remove();
|
|
||||||
removeTips();
|
|
||||||
});
|
|
||||||
//APP.display();
|
|
||||||
};
|
};
|
||||||
|
window.rc = requireConfig;
|
||||||
|
window.apiconf = ApiConfig;
|
||||||
|
$('#sbox-iframe').attr('src',
|
||||||
|
ApiConfig.httpSafeOrigin + '/todo/inner.html?' + requireConfig.urlArgs +
|
||||||
|
'#' + encodeURIComponent(JSON.stringify(req)));
|
||||||
|
|
||||||
// TODO make this actually work, and scroll to bottom...
|
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||||
var scrollTo = function (t) {
|
// loading screen setup.
|
||||||
var $list = $iframe.find('#tasksList');
|
var done = waitFor();
|
||||||
|
var onMsg = function (msg) {
|
||||||
$list.animate({
|
var data = JSON.parse(msg.data);
|
||||||
scrollTop: t,
|
if (data.q !== 'READY') { return; }
|
||||||
});
|
window.removeEventListener('message', onMsg);
|
||||||
|
var _done = done;
|
||||||
|
done = function () { };
|
||||||
|
_done();
|
||||||
};
|
};
|
||||||
scrollTo = scrollTo;
|
window.addEventListener('message', onMsg);
|
||||||
|
}).nThen(function (/*waitFor*/) {
|
||||||
var makeCheckbox = function (id, cb) {
|
var getSecrets = function (Cryptpad) {
|
||||||
var entry = APP.lm.proxy.data[id];
|
var proxy = Cryptpad.getProxy();
|
||||||
var checked = entry.state === 1? 'cp-task-checkbox-checked fa-check-square-o': 'cp-task-checkbox-unchecked fa-square-o';
|
var hash = proxy.todo || Cryptpad.createRandomHash();
|
||||||
|
return Cryptpad.getSecrets('todo', hash);
|
||||||
var title = entry.state === 1?
|
|
||||||
Messages.todo_markAsIncompleteTitle:
|
|
||||||
Messages.todo_markAsCompleteTitle;
|
|
||||||
title = title;
|
|
||||||
|
|
||||||
removeTips();
|
|
||||||
return $('<span>', {
|
|
||||||
'class': 'cp-task-checkbox fa ' + checked,
|
|
||||||
//title: title,
|
|
||||||
}).on('click', function () {
|
|
||||||
entry.state = (entry.state + 1) % 2;
|
|
||||||
if (typeof(cb) === 'function') {
|
|
||||||
cb(entry.state);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
SFCommonO.start({
|
||||||
var addTaskUI = function (el, animate) {
|
getSecrets: getSecrets,
|
||||||
var $taskDiv = $('<div>', {
|
noHash: true,
|
||||||
'class': 'cp-task'
|
});
|
||||||
});
|
|
||||||
if (animate) {
|
|
||||||
$taskDiv.prependTo($list);
|
|
||||||
} else {
|
|
||||||
$taskDiv.appendTo($list);
|
|
||||||
}
|
|
||||||
$taskDiv.data('id', el);
|
|
||||||
|
|
||||||
makeCheckbox(el, function (/*state*/) {
|
|
||||||
APP.display();
|
|
||||||
})
|
|
||||||
.appendTo($taskDiv);
|
|
||||||
|
|
||||||
var entry = APP.lm.proxy.data[el];
|
|
||||||
|
|
||||||
if (entry.state) {
|
|
||||||
$taskDiv.addClass('cp-task-complete');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('<span>', { 'class': 'cp-task-text' })
|
|
||||||
.text(entry.task)
|
|
||||||
.appendTo($taskDiv);
|
|
||||||
/*$('<span>', { 'class': 'cp-task-date' })
|
|
||||||
.text(new Date(entry.ctime).toLocaleString())
|
|
||||||
.appendTo($taskDiv);*/
|
|
||||||
$('<button>', {
|
|
||||||
'class': 'fa fa-times cp-task-remove btn btn-danger',
|
|
||||||
title: Messages.todo_removeTaskTitle,
|
|
||||||
}).appendTo($taskDiv).on('click', function() {
|
|
||||||
deleteTask(el);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (animate) {
|
|
||||||
$taskDiv.hide();
|
|
||||||
window.setTimeout(function () {
|
|
||||||
// ???
|
|
||||||
$taskDiv.fadeIn();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
removeTips();
|
|
||||||
};
|
|
||||||
var display = APP.display = function () {
|
|
||||||
$list.empty();
|
|
||||||
removeTips();
|
|
||||||
APP.lm.proxy.order.forEach(function (el) {
|
|
||||||
addTaskUI(el);
|
|
||||||
});
|
|
||||||
//scrollTo('300px');
|
|
||||||
};
|
|
||||||
|
|
||||||
var addTask = function () {
|
|
||||||
var $input = $iframe.find('#newTodoName');
|
|
||||||
// if the input is empty after removing leading and trailing spaces
|
|
||||||
// don't create a new entry
|
|
||||||
if (!$input.val().trim()) { return; }
|
|
||||||
|
|
||||||
var obj = {
|
|
||||||
"state": 0,
|
|
||||||
"task": $input.val(),
|
|
||||||
"ctime": +new Date(),
|
|
||||||
"mtime": +new Date()
|
|
||||||
};
|
|
||||||
|
|
||||||
var id = Cryptpad.createChannelId();
|
|
||||||
todo.add(id, obj);
|
|
||||||
|
|
||||||
$input.val("");
|
|
||||||
addTaskUI(id, true);
|
|
||||||
//display();
|
|
||||||
};
|
|
||||||
|
|
||||||
var $formSubmit = $iframe.find('.cp-create-form button').on('click', addTask);
|
|
||||||
$iframe.find('#newTodoName').on('keypress', function (e) {
|
|
||||||
switch (e.which) {
|
|
||||||
case 13:
|
|
||||||
$formSubmit.click();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(e.which);
|
|
||||||
}
|
|
||||||
}).focus();
|
|
||||||
|
|
||||||
var editTask = function () {
|
|
||||||
|
|
||||||
};
|
|
||||||
editTask = editTask;
|
|
||||||
|
|
||||||
display();
|
|
||||||
Cryptpad.removeLoadingScreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
var onInit = function () {
|
|
||||||
Cryptpad.addLoadingScreen();
|
|
||||||
|
|
||||||
$body.on('dragover', function (e) { e.preventDefault(); });
|
|
||||||
$body.on('drop', function (e) { e.preventDefault(); });
|
|
||||||
|
|
||||||
var Title;
|
|
||||||
var $bar = $iframe.find('.toolbar-container');
|
|
||||||
|
|
||||||
Title = Cryptpad.createTitle({}, function(){}, Cryptpad);
|
|
||||||
|
|
||||||
var configTb = {
|
|
||||||
displayed: ['useradmin', 'newpad', 'limit', 'upgrade', 'pageTitle'],
|
|
||||||
ifrw: ifrw,
|
|
||||||
common: Cryptpad,
|
|
||||||
//hideDisplayName: true,
|
|
||||||
$container: $bar,
|
|
||||||
pageTitle: Messages.todo_title
|
|
||||||
};
|
|
||||||
|
|
||||||
APP.toolbar = Toolbar.create(configTb);
|
|
||||||
APP.toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
|
|
||||||
};
|
|
||||||
|
|
||||||
var createTodo = function() {
|
|
||||||
var obj = Cryptpad.getProxy();
|
|
||||||
var hash = Cryptpad.createRandomHash();
|
|
||||||
|
|
||||||
if(obj.todo) {
|
|
||||||
hash = obj.todo;
|
|
||||||
} else {
|
|
||||||
obj.todo = hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
var secret = Cryptpad.getSecrets('todo', hash);
|
|
||||||
|
|
||||||
var listmapConfig = {
|
|
||||||
data: {},
|
|
||||||
websocketURL: Cryptpad.getWebsocketURL(),
|
|
||||||
channel: secret.channel,
|
|
||||||
validateKey: secret.keys.validateKey || undefined,
|
|
||||||
crypto: Crypto.createEncryptor(secret.keys),
|
|
||||||
userName: 'todo',
|
|
||||||
logLevel: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
var lm = APP.lm = Listmap.create(listmapConfig);
|
|
||||||
|
|
||||||
lm.proxy.on('create', onInit)
|
|
||||||
.on('ready', onReady);
|
|
||||||
};
|
|
||||||
|
|
||||||
Cryptpad.ready(function () {
|
|
||||||
createTodo();
|
|
||||||
Cryptpad.reportAppUsage();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ define([
|
|||||||
|
|
||||||
], function () {
|
], function () {
|
||||||
var Todo = {};
|
var Todo = {};
|
||||||
var Cryptpad;
|
|
||||||
|
|
||||||
/* data model
|
/* data model
|
||||||
{
|
{
|
||||||
@@ -60,9 +59,7 @@ define([
|
|||||||
if (proxy.data[id]) { delete proxy.data[id]; }
|
if (proxy.data[id]) { delete proxy.data[id]; }
|
||||||
};
|
};
|
||||||
|
|
||||||
Todo.init = function (proxy, common) {
|
Todo.init = function (proxy) {
|
||||||
Cryptpad = common;
|
|
||||||
|
|
||||||
var api = {};
|
var api = {};
|
||||||
initialize(proxy);
|
initialize(proxy);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ define([
|
|||||||
'/common/cryptget.js',
|
'/common/cryptget.js',
|
||||||
'/bower_components/nthen/index.js',
|
'/bower_components/nthen/index.js',
|
||||||
'/common/sframe-common.js',
|
'/common/sframe-common.js',
|
||||||
|
'/common/sframe-common-interface.js',
|
||||||
'/api/config',
|
'/api/config',
|
||||||
'/common/common-realtime.js',
|
'/common/common-realtime.js',
|
||||||
'/customize/pages.js',
|
'/customize/pages.js',
|
||||||
@@ -36,6 +37,7 @@ define([
|
|||||||
Cryptget,
|
Cryptget,
|
||||||
nThen,
|
nThen,
|
||||||
SFCommon,
|
SFCommon,
|
||||||
|
SFUI,
|
||||||
ApiConfig,
|
ApiConfig,
|
||||||
CommonRealtime,
|
CommonRealtime,
|
||||||
Pages,
|
Pages,
|
||||||
@@ -208,6 +210,7 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
var setEditable = function (bool) {
|
var setEditable = function (bool) {
|
||||||
|
APP.editable = bool;
|
||||||
if (readOnly && bool) { return; }
|
if (readOnly && bool) { return; }
|
||||||
if (bool) { $controls.css('display', 'flex'); }
|
if (bool) { $controls.css('display', 'flex'); }
|
||||||
else { $controls.hide(); }
|
else { $controls.hide(); }
|
||||||
@@ -237,17 +240,9 @@ define([
|
|||||||
APP.upload = function (title) {
|
APP.upload = function (title) {
|
||||||
var canvas = $canvas[0];
|
var canvas = $canvas[0];
|
||||||
APP.canvas.deactivateAll().renderAll();
|
APP.canvas.deactivateAll().renderAll();
|
||||||
var finish = function (thumb) {
|
canvas.toBlob(function (blob) {
|
||||||
canvas.toBlob(function (blob) {
|
blob.name = title;
|
||||||
blob.name = title;
|
APP.FM.handleFile(blob);
|
||||||
APP.FM.handleFile(blob, void 0, thumb);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Thumb.fromCanvas(canvas, function (e, blob) {
|
|
||||||
// carry on even if you can't get a thumbnail
|
|
||||||
if (e) { console.error(e); }
|
|
||||||
finish(blob);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -287,6 +282,7 @@ define([
|
|||||||
})
|
})
|
||||||
.on('dblclick', function (e) {
|
.on('dblclick', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!APP.editable) { return; }
|
||||||
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
||||||
$color.css({
|
$color.css({
|
||||||
'background-color': c,
|
'background-color': c,
|
||||||
@@ -378,6 +374,29 @@ define([
|
|||||||
onLocal();
|
onLocal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var initThumbnails = function () {
|
||||||
|
var oldThumbnailState;
|
||||||
|
var privateDat = metadataMgr.getPrivateData();
|
||||||
|
if (!privateDat.thumbnails) { return; }
|
||||||
|
var hash = privateDat.availableHashes.editHash ||
|
||||||
|
privateDat.availableHashes.viewHash;
|
||||||
|
var href = privateDat.pathname + '#' + hash;
|
||||||
|
var mkThumbnail = function () {
|
||||||
|
if (!hash) { return; }
|
||||||
|
if (initializing) { return; }
|
||||||
|
if (!APP.realtime) { return; }
|
||||||
|
var content = APP.realtime.getUserDoc();
|
||||||
|
if (content === oldThumbnailState) { return; }
|
||||||
|
var D = Thumb.getResizedDimensions($canvas[0], 'pad');
|
||||||
|
Thumb.fromCanvas($canvas[0], D, function (err, b64) {
|
||||||
|
oldThumbnailState = content;
|
||||||
|
Thumb.setPadThumbnail(href, b64);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
|
||||||
|
window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
|
||||||
|
};
|
||||||
|
|
||||||
config.onInit = function (info) {
|
config.onInit = function (info) {
|
||||||
updateLocalPalette(palette);
|
updateLocalPalette(palette);
|
||||||
readOnly = metadataMgr.getPrivateData().readOnly;
|
readOnly = metadataMgr.getPrivateData().readOnly;
|
||||||
@@ -538,6 +557,10 @@ define([
|
|||||||
initializing = false;
|
initializing = false;
|
||||||
config.onLocal();
|
config.onLocal();
|
||||||
Cryptpad.removeLoadingScreen();
|
Cryptpad.removeLoadingScreen();
|
||||||
|
|
||||||
|
initThumbnails();
|
||||||
|
|
||||||
|
|
||||||
if (readOnly) { return; }
|
if (readOnly) { return; }
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
common.openTemplatePicker();
|
common.openTemplatePicker();
|
||||||
|
|||||||
Reference in New Issue
Block a user