Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging
commit
dc783ce3e4
|
@ -38,7 +38,7 @@
|
|||
"scrypt-async": "1.2.0",
|
||||
"require-css": "0.1.10",
|
||||
"less": "^2.7.2",
|
||||
"bootstrap": "#v4.0.0-alpha.6",
|
||||
"bootstrap": "^v4.0.0",
|
||||
"diff-dom": "2.1.1",
|
||||
"nthen": "^0.1.5",
|
||||
"open-sans-fontface": "^1.4.2",
|
||||
|
@ -49,6 +49,6 @@
|
|||
"sortablejs": "#^1.6.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"bootstrap": "v4.0.0-alpha.6"
|
||||
"bootstrap": "^v4.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ define([
|
|||
logLevel: 1,
|
||||
classic: true,
|
||||
ChainPad: ChainPad,
|
||||
owners: [opt.edPublic]
|
||||
};
|
||||
|
||||
var rt = opt.rt = Listmap.create(config);
|
||||
|
|
|
@ -384,7 +384,8 @@ define([
|
|||
var item = faq[c][q];
|
||||
if (typeof item !== "object") { return; }
|
||||
var answer = h('p.cp-faq-questions-a');
|
||||
var question = h('p.cp-faq-questions-q');
|
||||
var hash = c + '-' + q;
|
||||
var question = h('p.cp-faq-questions-q#' + hash);
|
||||
$(question).click(function () {
|
||||
if ($(answer).is(':visible')) {
|
||||
return void $(answer).slideUp();
|
||||
|
@ -401,6 +402,10 @@ define([
|
|||
h('div.cp-faq-category-questions', questions)
|
||||
]));
|
||||
});
|
||||
var hash = window.location.hash;
|
||||
if (hash) {
|
||||
$(categories).find(hash).click();
|
||||
}
|
||||
return h('div#cp-main', [
|
||||
infopageTopbar(),
|
||||
h('div.container.cp-container', [
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
@import (once) "./colortheme-all.less";
|
||||
|
||||
.checkmark_main(@size) {
|
||||
|
||||
@width: round(@size / 8);
|
||||
@dim1: round(@size / 3);
|
||||
@dim2: round(2 * @size / 3);
|
||||
@top: round(@size / 12);
|
||||
// <label.cp-checkmark><input><span.cp-checkmark-mark></span>Text</label>
|
||||
.cp-checkmark {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
&.cp-checkmark-secondary {
|
||||
.cp-checkmark-mark {
|
||||
&:after {
|
||||
border-color: @colortheme_checkmark-col2;
|
||||
}
|
||||
}
|
||||
input {
|
||||
&:checked ~ .cp-checkmark-mark {
|
||||
background-color: @colortheme_checkmark-back2;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover .cp-checkmark-mark {
|
||||
background-color: @colortheme_checkmark-back0-active;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
&:checked ~ .cp-checkmark-mark {
|
||||
background-color: @colortheme_checkmark-back1;
|
||||
&:after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-checkmark-mark {
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
height: @size;
|
||||
width: @size;
|
||||
background-color: @colortheme_checkmark-back0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&:after {
|
||||
content: "";
|
||||
display: none;
|
||||
margin-top: @top;
|
||||
width: @dim1;
|
||||
height: @dim2;
|
||||
transform: rotate(45deg);
|
||||
border: solid @colortheme_checkmark-col1;
|
||||
border-width: 0 @width @width 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -109,3 +109,10 @@
|
|||
@cryptpad_color_grey: #999999;
|
||||
@cryptpad_header_col: #1E1F1F;
|
||||
@cryptpad_text_col: #3F4141;
|
||||
|
||||
@colortheme_checkmark-back0: #ffffff;
|
||||
@colortheme_checkmark-back0-active: #bbbbbb;
|
||||
@colortheme_checkmark-back1: #FF0073;
|
||||
@colortheme_checkmark-col1: #ffffff;
|
||||
@colortheme_checkmark-back2: #FFFFFF;
|
||||
@colortheme_checkmark-col2: #000000;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
@import (once) "./colortheme-all.less";
|
||||
@import (once) "./tools.less";
|
||||
@import (once) "./checkmark.less";
|
||||
@import (once) './icon-colors.less';
|
||||
|
||||
.creation_main() {
|
||||
.tippy-popper {
|
||||
|
@ -31,149 +33,220 @@
|
|||
max-width: 100%;
|
||||
margin: 40px auto;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
h2 {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
justify-content: space-between;
|
||||
.cp-creation-help {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.cp-creation-help-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
p {
|
||||
padding: 0 20px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 50%;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 500px) {
|
||||
width: ~"calc(100% - 30px)";
|
||||
}
|
||||
@media screen and (max-height: 800px), screen and (max-width: 500px) {
|
||||
h2 .cp-creation-help {
|
||||
display: inline;
|
||||
}
|
||||
.cp-creation-help-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media screen and (min-height: 601px) {
|
||||
@media screen and (min-width: 501px) {
|
||||
p {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-creation-create, .cp-creation-settings {
|
||||
margin-top: 0px;
|
||||
@creation-button: #FF0073;
|
||||
button {
|
||||
.tools_unselectable();
|
||||
padding: 15px;
|
||||
background: darken(@colortheme_loading-bg, 10%);
|
||||
color: @colortheme_loading-color;
|
||||
background: #30b239;
|
||||
color: #FFF;
|
||||
font-weight: bold;
|
||||
margin: 3px 10px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
background: darken(@colortheme_loading-bg, 5%);
|
||||
background: darken(@creation-button, 5%);
|
||||
}
|
||||
&.cp-creation-button-selected {
|
||||
color: darken(@colortheme_loading-bg, 10%);
|
||||
background: @colortheme_loading-color;
|
||||
}
|
||||
}
|
||||
.cp-creation-create {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#cp-creation-form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
& > div {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
label {
|
||||
flex: 1;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
&+ label {
|
||||
margin-bottom: 0;
|
||||
flex: 1;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
.cp-creation-help {
|
||||
font-size: 18px;
|
||||
color: white;
|
||||
&:hover {
|
||||
color: #AAA;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-creation-slider {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
max-height: 0px;
|
||||
transition: max-height 0.5s ease-in-out;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
&.active {
|
||||
max-height: 40px;
|
||||
}
|
||||
}
|
||||
.cp-creation-expire {
|
||||
.cp-creation-expire-picker {
|
||||
text-align: center;
|
||||
input {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-creation-settings {
|
||||
button {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.cp-filler { flex: 1; }
|
||||
}
|
||||
|
||||
div.cp-creation-remember {
|
||||
margin-top: 30px;
|
||||
.cp-creation-remember-help {
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
div.cp-creation-template {
|
||||
width: 100%;
|
||||
background-color: darken(@colortheme_modal-bg, 3%);
|
||||
padding: 20px;
|
||||
margin: 30px 0;
|
||||
.cp-creation-title {
|
||||
padding: 0 0 10px 10px;
|
||||
}
|
||||
}
|
||||
.cp-creation-template-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
align-items: center;
|
||||
.cp-creation-template-element {
|
||||
@darker: darken(@colortheme_modal-fg, 30%);
|
||||
|
||||
width: 135px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
display: inline-flex;
|
||||
flex-flow: column;
|
||||
|
||||
box-sizing: content-box;
|
||||
|
||||
text-align: left;
|
||||
line-height: 1em;
|
||||
cursor: pointer;
|
||||
|
||||
background-color: #111;
|
||||
color: @darker;
|
||||
border: 1px solid transparent;
|
||||
|
||||
&.cp-creation-template-selected {
|
||||
border: 1px solid white;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
transition: all 0.1s;
|
||||
|
||||
&:hover {
|
||||
color: @colortheme_modal-fg;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cp-creation-template-element-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin-top: 5px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.fa {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 70px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
display: none;
|
||||
&:checked {
|
||||
& + label {
|
||||
font-weight: bold;
|
||||
background-color: lighten(@colortheme_loading-bg, 20%);
|
||||
cursor: default;
|
||||
border: 1px solid #c1158e;
|
||||
&:hover {
|
||||
background-color: lighten(@colortheme_loading-bg, 20%);
|
||||
}
|
||||
}
|
||||
.cp-creation-deleted-container {
|
||||
text-align: center;
|
||||
.cp-creation-deleted {
|
||||
background: #111;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
input[type="radio"] + label {
|
||||
.tools_unselectable();
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
padding: 5px;
|
||||
margin: 0 20px;
|
||||
border: 1px solid @colortheme_loading-color;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: lighten(@colortheme_loading-bg, 10%);
|
||||
|
||||
.checkmark_main(30px);
|
||||
|
||||
@media screen and (max-width: @browser_media-narrow-screen) {
|
||||
& > div {
|
||||
width: 95%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
}
|
||||
.cp-creation-expire {
|
||||
#cp-creation-expire-true {
|
||||
display: none;
|
||||
&:checked {
|
||||
& + label {
|
||||
height: 100px;
|
||||
.cp-creation-expire-picker {
|
||||
display: inline;
|
||||
@media screen and (max-width: @browser_media-medium-screen) {
|
||||
#cp-creation-form {
|
||||
div.cp-creation-template {
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
.cp-creation-template-container {
|
||||
.cp-creation-template-element {
|
||||
flex-flow: row;
|
||||
margin: 1px;
|
||||
padding: 5px;
|
||||
width: 155px;
|
||||
img {
|
||||
display: none;
|
||||
}
|
||||
.fa {
|
||||
font-size: 18px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
display: inline !important;
|
||||
}
|
||||
.cp-creation-template-element-name {
|
||||
margin: 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
label[for="cp-creation-expire-true"] {
|
||||
flex-wrap: wrap;
|
||||
.cp-creation-expire-picker {
|
||||
display: none;
|
||||
}
|
||||
input {
|
||||
width: 70px;
|
||||
}
|
||||
select {
|
||||
width: 100px;
|
||||
}
|
||||
input, select {
|
||||
border: none;
|
||||
height: 30px;
|
||||
background: @colortheme_loading-bg;
|
||||
color: @colortheme_loading-color;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-creation-settings {
|
||||
justify-content: left;
|
||||
a {
|
||||
color: #0275d8;
|
||||
&:hover {
|
||||
color: lighten(#0275d8, 10%);
|
||||
}
|
||||
}
|
||||
&> span.fa {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
.cp-creation-deleted {
|
||||
background: #111;
|
||||
padding: 10px;
|
||||
text-align: justify;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,8 @@ $(function () {
|
|||
require([ '/customize/main.js', ], function () {});
|
||||
} else if (/invite/.test(pathname)) {
|
||||
require([ '/invite/main.js'], function () {});
|
||||
} else if (/faq/.test(pathname)) {
|
||||
window.location.hash = window.location.hash;
|
||||
} else {
|
||||
require([ '/customize/main.js', ], function () {});
|
||||
}
|
||||
|
|
|
@ -363,7 +363,7 @@ define(function () {
|
|||
out.fm_templateName = "Modèles";
|
||||
out.fm_searchName = "Recherche";
|
||||
out.fm_recentPadsName = "Pads récents";
|
||||
out.fm_ownedPadsName = "Pads possédés";
|
||||
out.fm_ownedPadsName = "Pads en votre possession";
|
||||
out.fm_searchPlaceholder = "Rechercher...";
|
||||
out.fm_newButton = "Nouveau";
|
||||
out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant";
|
||||
|
@ -771,13 +771,13 @@ define(function () {
|
|||
'Il désigne un document que vous pouvez modifier dans votre navigateur et, en général, vous pouvez voir les modifications effectuées par les autres utilisateurs de manière quasiment instantanée.'
|
||||
},
|
||||
owned: {
|
||||
q: "Qu'est-ce qu'un pad possédé ?",
|
||||
a: "Un <em>pad possédé</em> est un pad créé avec un <em>propriétaire</em> explicite, identifié sur le serveur par sa <em>clé de signature publique</em>.<br>" +
|
||||
q: "Qu'est-ce qu'un pad avec propriétaire ?",
|
||||
a: "Être <em>propriétaire</em> d'un pad signifie que vous êtes identifié comme tel par le serveur avec à votre <em>clé de signature publique</em>.<br>" +
|
||||
"Le propriétaire d'un pad peut décider de supprimer ce pad du serveur de manière permanente, afin de le rendre inaccessible aux autres collaborateurs même s'ils possédent le lien dans leur CryptDrive."
|
||||
},
|
||||
expiring: {
|
||||
q: "Qu'est-ce qu'un pad expirant ?",
|
||||
a: "Un <em>pad expirant</em> est un pad créé avec une date définie à partir de laquelle il sera supprimé automatiquement du serveur. Les pads expirants peuvent être configurés pour avoir une durée de vie comprise entre une heure et cent mois. Le pad et tout son historique sera alors inaccessible, de manière permanente, même s'il est en cours d'édition à sa date d'expiration.<br>" +
|
||||
q: "Qu'est-ce qu'un pad à durée de vie ?",
|
||||
a: "Un <em>pad à durée de vie</em> est un pad créé avec une date définie à partir de laquelle il sera supprimé automatiquement du serveur. Ils peuvent être configurés pour avoir une durée de vie comprise entre une heure et cent mois. Le pad et tout son historique sera alors inaccessible, de manière permanente, même s'il est en cours d'édition à sa date d'expiration.<br>" +
|
||||
"Si un pad possède une date d'expiration, vous pouvez la vérifier en regardant les <em>propriétés</em> du pad, soit avec un clic-droit sur le pad dans votre CryptDrive, ou soit en cliquant sur Propriétés dans le sous-menu de la barre d'outils de l'application."
|
||||
},
|
||||
tag: {
|
||||
|
@ -871,9 +871,9 @@ define(function () {
|
|||
},
|
||||
remove: {
|
||||
q: "J'ai supprimé un pad ou un fichier de mon CryptDrive, mais le contenu est encore disponible. Comment le supprimer ?",
|
||||
a: "Seuls les <em>pads possédés</em> (introduits en février 2018) peuvent être supprimés du serveur. Ils ne peuvent d'ailleurs être supprimés du serveur que par leur <em>propriétaire</em> (l'utilisateur ayant créé le pad).<br>" +
|
||||
a: "Seuls les <em>pads avec propriétaire</em> (introduits en février 2018) peuvent être supprimés du serveur. Ils ne peuvent d'ailleurs être supprimés du serveur que par leur <em>propriétaire</em> (l'utilisateur ayant créé le pad).<br>" +
|
||||
"Si vous n'êtes pas le créateur du pad, vous devrez demander au propriétaire de le supprimer pour vous.<br>" +
|
||||
"Pour les pads que vous possédez, vous pouvez effectuer un <b>clic-droit sur le pad dans votre CryptDrive</b>, et sélectionner <b>Supprimer du serveur</b>."
|
||||
"Pour les pads dont vous êtes le propriétaire, vous pouvez effectuer un <b>clic-droit sur le pad dans votre CryptDrive</b>, et sélectionner <b>Supprimer du serveur</b>."
|
||||
},
|
||||
forget: {
|
||||
q: "Que faire si j'oublie mon mot de passe ?",
|
||||
|
@ -1062,32 +1062,35 @@ define(function () {
|
|||
// Creation page
|
||||
out.creation_404 = "Ce pad n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant.";
|
||||
out.creation_ownedTitle = "Type de pad";
|
||||
out.creation_ownedTrue = "Pad possédé";
|
||||
out.creation_ownedFalse = "Pad ouvert";
|
||||
out.creation_owned1 = "Un pad <b>possédé</b> peut être supprimé du serveur à tout moment quand son propriétaire le souhaite. Une fois supprimé, il disparaît du CryptDrive des autres utilisateurs.";
|
||||
out.creation_owned2 = "Un pad <b>ouvert</b> n'a pas de propriétaire et ne peut donc pas être supprimé du serveur à moins d'avoir dépassé sa date d'expiration.";
|
||||
out.creation_owned = "Être propriétaire de ce pad";
|
||||
out.creation_ownedTrue = "Être propriétaire";
|
||||
out.creation_ownedFalse = "Pas de propriétaire";
|
||||
out.creation_owned1 = "Être <b>propriétaire</b> d'un pad signifie que vous pouvez le supprimer du serveur à tout moment. Une fois supprimé, il disparaît du CryptDrive des autres utilisateurs.";
|
||||
out.creation_owned2 = "Un pad <b>sans propriétaire</b> ne peut pas être supprimé du serveur à moins d'avoir dépassé son éventuelle date d'expiration.";
|
||||
out.creation_expireTitle = "Durée de vie";
|
||||
out.creation_expire = "Ajouter une durée de vie";
|
||||
out.creation_expireTrue = "Ajouter durée de vie";
|
||||
out.creation_expireFalse = "Illimité";
|
||||
out.creation_expireHours = "Heure(s)";
|
||||
out.creation_expireDays = "Jour(s)";
|
||||
out.creation_expireMonths = "Mois";
|
||||
out.creation_expire1 = "Un pad <b>illimité</b> ne sera pas supprimé du serveur à moins que son propriétaire ne le décide.";
|
||||
out.creation_expire2 = "Un pad <b>expirant</b> a une durée de vie définie, après laquelle il sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs.";
|
||||
out.creation_createTitle = "Créer un pad";
|
||||
out.creation_createFromTemplate = "Depuis un modèle";
|
||||
out.creation_createFromScratch = "Nouveau pad vide";
|
||||
out.creation_settings = "Préférences des nouveaux pads";
|
||||
out.creation_saveSettings = "Sauver les préférences";
|
||||
out.creation_expire2 = "Un pad <b>à durée de vie</b> sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs lorsque cette durée sera dépassée.";
|
||||
out.creation_noTemplate = "Pas de modèle";
|
||||
out.creation_newTemplate = "Nouveau modèle";
|
||||
out.creation_create = "Créer";
|
||||
out.creation_saveSettings = "Ne plus me demander";
|
||||
out.creation_settings = "Voir davantage de préférences";
|
||||
out.creation_rememberHelp = "Ouvrez votre page de Préférences pour annuler cette fonctionnalité";
|
||||
// Properties about creation data
|
||||
out.creation_owners = "Propriétaires";
|
||||
out.creation_ownedByOther = "Possédé par un autre utilisateur";
|
||||
out.creation_ownedByOther = "Appartient à un autre utilisateur";
|
||||
out.creation_noOwner = "Pas de propriétaire";
|
||||
out.creation_expiration = "Date d'expiration";
|
||||
out.creation_propertiesTitle = "Disponibilité";
|
||||
out.creation_appMenuName = "Mode avancé (Ctrl + E)";
|
||||
out.creation_newPadModalDescription = "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur <b>Tab</b> pour sélectionner un type et appuyer sur <b>Entrée</b> pour valider.";
|
||||
out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads possédés ou à date d'expiration). Vous pouvez appuyer sur <b>Espace</b> pour changer sa valeur.";
|
||||
out.creation_newPadModalDescriptionAdvanced = "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur <b>Espace</b> pour changer sa valeur.";
|
||||
out.creation_newPadModalAdvanced = "Afficher l'écran de création de pads";
|
||||
|
||||
// New share modal
|
||||
|
|
|
@ -1109,11 +1109,13 @@ define(function () {
|
|||
// Creation page
|
||||
out.creation_404 = "This pad not longer exists. Use the following form to create a new pad.";
|
||||
out.creation_ownedTitle = "Type of pad";
|
||||
out.creation_ownedTrue = "Owned pad";
|
||||
out.creation_owned = "Owned pad"; // Creation page
|
||||
out.creation_ownedTrue = "Owned pad"; // Settings
|
||||
out.creation_ownedFalse = "Open pad";
|
||||
out.creation_owned1 = "An <b>owned</b> pad can be deleted from the server whenever the owner wants. Deleting an owned pad removes it from other users' CryptDrives.";
|
||||
out.creation_owned2 = "An <b>open</b> pad doesn't have any owner and thus, it can't be deleted from the server unless it has reached its expiration time.";
|
||||
out.creation_expireTitle = "Life time";
|
||||
out.creation_expire = "Expiring pad";
|
||||
out.creation_expireTrue = "Add a life time";
|
||||
out.creation_expireFalse = "Unlimited";
|
||||
out.creation_expireHours = "Hour(s)";
|
||||
|
@ -1121,11 +1123,12 @@ define(function () {
|
|||
out.creation_expireMonths = "Month(s)";
|
||||
out.creation_expire1 = "An <b>unlimited</b> pad will not be removed from the server until its owner deletes it.";
|
||||
out.creation_expire2 = "An <b>expiring</b> pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives.";
|
||||
out.creation_createTitle = "Create a pad";
|
||||
out.creation_createFromTemplate = "From template";
|
||||
out.creation_createFromScratch = "From scratch";
|
||||
out.creation_settings = "New Pad settings";
|
||||
out.creation_saveSettings = "Save settings";
|
||||
out.creation_noTemplate = "No template";
|
||||
out.creation_newTemplate = "New template";
|
||||
out.creation_create = "Create";
|
||||
out.creation_saveSettings = "Don't show this again";
|
||||
out.creation_settings = "View more settings";
|
||||
out.creation_rememberHelp = "Visit your Settings page to reset this preference";
|
||||
// Properties about creation data
|
||||
out.creation_owners = "Owners";
|
||||
out.creation_ownedByOther = "Owned by another user";
|
||||
|
|
|
@ -638,9 +638,10 @@ define([
|
|||
var $icon = UI.getIcon();
|
||||
if (!data) { return $icon; }
|
||||
var href = data.href;
|
||||
if (!href) { return $icon; }
|
||||
var type = data.type;
|
||||
if (!href && !type) { return $icon; }
|
||||
|
||||
var type = Hash.parsePadUrl(href).type;
|
||||
if (!type) { type = Hash.parsePadUrl(href).type; }
|
||||
$icon = UI.getIcon(type);
|
||||
|
||||
return $icon;
|
||||
|
|
|
@ -237,6 +237,9 @@ define([
|
|||
$span.prepend(img);
|
||||
cb($(img));
|
||||
};
|
||||
Thumb.addThumbnail = function(thumb, $span, cb) {
|
||||
return addThumbnail(null, thumb, $span, cb);
|
||||
};
|
||||
var getKey = function (href) {
|
||||
var parsed = Hash.parsePadUrl(href);
|
||||
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
|
||||
|
|
|
@ -852,7 +852,7 @@ define([
|
|||
var tbState = true;
|
||||
common.getAttribute(['general', 'markdown-help'], function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if ($(window).height() < 800) { return; }
|
||||
if ($(window).height() < 800 && $(window).width() < 800) { return; }
|
||||
if (data === true && $toolbarButton.length && tbState) {
|
||||
$toolbarButton.click();
|
||||
}
|
||||
|
@ -869,7 +869,7 @@ define([
|
|||
}
|
||||
common.getAttribute(['general', 'markdown-help'], function (e, data) {
|
||||
if (e) { return void console.error(e); }
|
||||
if ($(window).height() < 800) { return; }
|
||||
if ($(window).height() < 800 && $(window).width() < 800) { return; }
|
||||
if (data === true && $toolbarButton) {
|
||||
// Show the toolbar using the button to make sure the icon in the button is
|
||||
// correct (caret-down / caret-up)
|
||||
|
@ -964,7 +964,7 @@ define([
|
|||
});
|
||||
|
||||
common.getAttribute(['hideHelp', type], function (err, val) {
|
||||
if ($(window).height() < 800) { return void toggleHelp(true); }
|
||||
if ($(window).height() < 800 && $(window).width() < 800) { return void toggleHelp(true); }
|
||||
if (val === true) { toggleHelp(true); }
|
||||
});
|
||||
|
||||
|
@ -1792,6 +1792,7 @@ define([
|
|||
|
||||
UIElements.setExpirationValue = function (val, $expire) {
|
||||
if (val && typeof (val) === "number") {
|
||||
$expire.find('#cp-creation-expire').attr('checked', true).trigger('change');
|
||||
$expire.find('#cp-creation-expire-true').attr('checked', true);
|
||||
if (val % (3600 * 24 * 30) === 0) {
|
||||
$expire.find('#cp-creation-expire-unit').val("month");
|
||||
|
@ -1822,22 +1823,24 @@ define([
|
|||
var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
|
||||
var $creation = $('<div>', { id: 'cp-creation', tabindex: 1 }).appendTo($creationContainer);
|
||||
|
||||
var setHTML = function (e, html) {
|
||||
e.innerHTML = html;
|
||||
return e;
|
||||
};
|
||||
|
||||
// Title
|
||||
$creation.append(h('h1.cp-creation-title', Messages['button_new'+type]));
|
||||
var colorClass = 'cp-icon-color-'+type;
|
||||
$creation.append(h('h2.cp-creation-title', Messages.newButtonTitle));
|
||||
//$creation.append(h('h2.cp-creation-title.'+colorClass, Messages.newButtonTitle));
|
||||
|
||||
// Deleted pad warning
|
||||
if (metadataMgr.getPrivateData().isDeleted) {
|
||||
$creation.append(h('div.cp-creation-deleted', Messages.creation_404));
|
||||
$creation.append(h('div.cp-creation-deleted-container',
|
||||
h('div.cp-creation-deleted', Messages.creation_404)
|
||||
));
|
||||
}
|
||||
|
||||
var createHelper = function (text) {
|
||||
var q = h('span.cp-creation-help.fa.fa-question', {
|
||||
title: text
|
||||
var origin = common.getMetadataMgr().getPrivateData().origin;
|
||||
var createHelper = function (href, text) {
|
||||
var q = h('a.cp-creation-help.fa.fa-question', {
|
||||
title: text,
|
||||
href: origin + href,
|
||||
target: "_blank"
|
||||
});
|
||||
return q;
|
||||
};
|
||||
|
@ -1845,78 +1848,159 @@ define([
|
|||
// Owned pads
|
||||
// Default is Owned pad
|
||||
var owned = h('div.cp-creation-owned', [
|
||||
h('h2', [
|
||||
Messages.creation_ownedTitle,
|
||||
createHelper(Messages.creation_owned1 + '\n' + Messages.creation_owned2)
|
||||
h('label.cp-checkmark', [
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
id: 'cp-creation-owned',
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('span.cp-checkmark-mark'),
|
||||
Messages.creation_owned
|
||||
]),
|
||||
h('div.cp-creation-help-container', [
|
||||
setHTML(h('p'), Messages.creation_owned1),
|
||||
setHTML(h('p'), Messages.creation_owned2)
|
||||
]),
|
||||
h('input#cp-creation-owned-true.cp-creation-owned-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-owned',
|
||||
value: 1,
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-owned-true' }, Messages.creation_ownedTrue),
|
||||
h('input#cp-creation-owned-false.cp-creation-owned-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-owned',
|
||||
value: 0
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-owned-false' }, Messages.creation_ownedFalse)
|
||||
createHelper('/faq.html#keywords-owned', Messages.creation_owned1)
|
||||
]);
|
||||
$creation.append(owned);
|
||||
|
||||
// If set to "open pad", check "open pad"
|
||||
if (!cfg.owned && typeof cfg.owned !== "undefined") {
|
||||
$creation.find('#cp-creation-owned-false').attr('checked', true);
|
||||
}
|
||||
|
||||
// Life time
|
||||
var expire = h('div.cp-creation-expire', [
|
||||
h('h2', [
|
||||
Messages.creation_expireTitle,
|
||||
createHelper(Messages.creation_expire1, Messages.creation_expire2)
|
||||
h('label.cp-checkmark', [
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
id: 'cp-creation-expire'
|
||||
}),
|
||||
h('span.cp-checkmark-mark'),
|
||||
Messages.creation_expire
|
||||
]),
|
||||
h('div.cp-creation-help-container', [
|
||||
setHTML(h('p'), Messages.creation_expire1),
|
||||
setHTML(h('p'), Messages.creation_expire2)
|
||||
]),
|
||||
h('input#cp-creation-expire-false.cp-creation-expire-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-expire',
|
||||
value: 0,
|
||||
checked: 'checked'
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-expire-false' }, Messages.creation_expireFalse),
|
||||
h('input#cp-creation-expire-true.cp-creation-expire-value', {
|
||||
type: 'radio',
|
||||
name: 'cp-creation-expire',
|
||||
value: 1
|
||||
}),
|
||||
h('label', { 'for': 'cp-creation-expire-true' }, [
|
||||
Messages.creation_expireTrue,
|
||||
h('span.cp-creation-expire-picker', [
|
||||
h('input#cp-creation-expire-val', {
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
value: 3
|
||||
}),
|
||||
h('select#cp-creation-expire-unit', [
|
||||
h('option', { value: 'hour' }, Messages.creation_expireHours),
|
||||
h('option', { value: 'day' }, Messages.creation_expireDays),
|
||||
h('option', {
|
||||
value: 'month',
|
||||
selected: 'selected'
|
||||
}, Messages.creation_expireMonths)
|
||||
])
|
||||
createHelper('faq.html#keywords-expiring', Messages.creation_expire2),
|
||||
h('div.cp-creation-expire-picker.cp-creation-slider', [
|
||||
h('input#cp-creation-expire-val', {
|
||||
type: "number",
|
||||
min: 1,
|
||||
max: 100,
|
||||
value: 3
|
||||
}),
|
||||
h('select#cp-creation-expire-unit', [
|
||||
h('option', { value: 'hour' }, Messages.creation_expireHours),
|
||||
h('option', { value: 'day' }, Messages.creation_expireDays),
|
||||
h('option', {
|
||||
value: 'month',
|
||||
selected: 'selected'
|
||||
}, Messages.creation_expireMonths)
|
||||
])
|
||||
])
|
||||
]);
|
||||
$creation.append(expire);
|
||||
|
||||
var templates = h('div.cp-creation-template', [
|
||||
h('h3.cp-creation-title.'+colorClass, Messages['button_new'+type]),
|
||||
h('div.cp-creation-template-container', [
|
||||
h('span.fa.fa-circle-o-notch.fa-spin.fa-4x.fa-fw')
|
||||
])
|
||||
]);
|
||||
|
||||
var settings = h('div.cp-creation-remember', [
|
||||
h('label.cp-checkmark', [
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
id: 'cp-creation-remember'
|
||||
}),
|
||||
h('span.cp-checkmark-mark'),
|
||||
Messages.creation_saveSettings
|
||||
]),
|
||||
createHelper('/settings/#creation', Messages.creation_settings),
|
||||
h('div.cp-creation-remember-help.cp-creation-slider', Messages.creation_rememberHelp)
|
||||
]);
|
||||
|
||||
var createDiv = h('div.cp-creation-create');
|
||||
var $create = $(createDiv);
|
||||
|
||||
$(h('div#cp-creation-form', [
|
||||
owned,
|
||||
expire,
|
||||
settings,
|
||||
templates,
|
||||
createDiv
|
||||
])).appendTo($creation);
|
||||
|
||||
// Display templates
|
||||
var selected = 0;
|
||||
sframeChan.query("Q_CREATE_TEMPLATES", type, function (err, res) {
|
||||
if (!res.data || !Array.isArray(res.data)) {
|
||||
return void console.error("Error: get the templates list");
|
||||
}
|
||||
var data = res.data.slice().sort(function (a, b) {
|
||||
if (a.name === b.name) { return 0; }
|
||||
return a.name < b.name ? -1 : 1;
|
||||
});
|
||||
data.unshift({
|
||||
name: Messages.creation_noTemplate,
|
||||
id: 0,
|
||||
icon: h('span.fa.fa-file')
|
||||
});
|
||||
data.push({
|
||||
name: Messages.creation_newTemplate,
|
||||
id: -1,
|
||||
icon: h('span.fa.fa-bookmark')
|
||||
});
|
||||
var $container = $(templates).find('.cp-creation-template-container').html('');
|
||||
data.forEach(function (obj, idx) {
|
||||
var name = obj.name;
|
||||
var $span = $('<span>', {
|
||||
'class': 'cp-creation-template-element',
|
||||
'title': name,
|
||||
}).appendTo($container);
|
||||
console.log(obj.id);
|
||||
$span.data('id', obj.id);
|
||||
if (idx === 0) { $span.addClass('cp-creation-template-selected'); }
|
||||
$span.append(obj.icon || UI.getFileIcon({type: type}));
|
||||
$('<span>', {'class': 'cp-creation-template-element-name'}).text(name)
|
||||
.appendTo($span);
|
||||
$span.click(function () {
|
||||
$container.find('.cp-creation-template-selected')
|
||||
.removeClass('cp-creation-template-selected');
|
||||
$span.addClass('cp-creation-template-selected');
|
||||
selected = idx;
|
||||
});
|
||||
|
||||
// Add thumbnail if it exists
|
||||
if (obj.thumbnail) {
|
||||
common.addThumbnail(obj.thumbnail, $span, function () {});
|
||||
}
|
||||
});
|
||||
});
|
||||
// Change template selection when Tab is pressed
|
||||
var next = function (revert) {
|
||||
var max = $creation.find('.cp-creation-template-element').length;
|
||||
selected = revert ?
|
||||
(--selected < 0 ? max-1 : selected) :
|
||||
++selected % max;
|
||||
$creation.find('.cp-creation-template-element')
|
||||
.removeClass('cp-creation-template-selected');
|
||||
$($creation.find('.cp-creation-template-element').get(selected))
|
||||
.addClass('cp-creation-template-selected');
|
||||
};
|
||||
|
||||
|
||||
// Display expiration form when checkbox checked
|
||||
$creation.find('#cp-creation-expire').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$creation.find('.cp-creation-expire-picker:not(.active)').addClass('active');
|
||||
$creation.find('#cp-creation-expire-val').focus();
|
||||
return;
|
||||
}
|
||||
$creation.find('.cp-creation-expire-picker').removeClass('active');
|
||||
$creation.focus();
|
||||
});
|
||||
|
||||
// Display settings help when checkbox checked
|
||||
$creation.find('#cp-creation-remember').on('change', function () {
|
||||
if ($(this).is(':checked')) {
|
||||
$creation.find('.cp-creation-remember-help:not(.active)').addClass('active');
|
||||
return;
|
||||
}
|
||||
$creation.find('.cp-creation-remember-help').removeClass('active');
|
||||
$creation.focus();
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
$creation.find('#cp-creation-expire-val').keydown(function (e) {
|
||||
if (e.which === 9) {
|
||||
e.stopPropagation();
|
||||
|
@ -1928,15 +2012,23 @@ define([
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
// Initial values
|
||||
if (!cfg.owned && typeof cfg.owned !== "undefined") {
|
||||
$creation.find('#cp-creation-owned').attr('checked', false);
|
||||
}
|
||||
if (cfg.skip) {
|
||||
$creation.find('#cp-creation-remember').attr('checked', 'checked');
|
||||
}
|
||||
UIElements.setExpirationValue(cfg.expire, $creation);
|
||||
|
||||
// Create the pad
|
||||
var getFormValues = function (template) {
|
||||
var getFormValues = function () {
|
||||
// Type of pad
|
||||
var ownedVal = parseInt($('input[name="cp-creation-owned"]:checked').val());
|
||||
var ownedVal = $('#cp-creation-owned').is(':checked') ? 1 : 0;
|
||||
// Life time
|
||||
var expireVal = 0;
|
||||
if(parseInt($('input[name="cp-creation-expire"]:checked').val())) {
|
||||
if($('#cp-creation-expire').is(':checked')) {
|
||||
var unit = 0;
|
||||
switch ($('#cp-creation-expire-unit').val()) {
|
||||
case "hour" : unit = 3600; break;
|
||||
|
@ -1947,62 +2039,48 @@ define([
|
|||
expireVal = ($('#cp-creation-expire-val').val() || 0) * unit;
|
||||
}
|
||||
|
||||
var $template = $creation.find('.cp-creation-template-selected');
|
||||
var templateId = $template.data('id') || undefined;
|
||||
|
||||
return {
|
||||
owned: ownedVal,
|
||||
expire: expireVal,
|
||||
template: template
|
||||
templateId: templateId
|
||||
};
|
||||
};
|
||||
var create = function (template) {
|
||||
$creationContainer.remove();
|
||||
var create = function () {
|
||||
var val = getFormValues();
|
||||
|
||||
common.createPad(getFormValues(template), function () {
|
||||
var skip = $('#cp-creation-remember').is(':checked');
|
||||
common.setAttribute(['general', 'creation', 'skip'], skip, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
});
|
||||
common.setAttribute(['general', 'creation', 'noTemplate'], skip, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
});
|
||||
|
||||
common.setAttribute(['general', 'creation', 'owned'], val.owned, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
});
|
||||
common.setAttribute(['general', 'creation', 'expire'], val.expire, function (e) {
|
||||
if (e) { return void console.error(e); }
|
||||
});
|
||||
|
||||
$creationContainer.remove();
|
||||
common.createPad(val, function () {
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
var $create = $(h('div.cp-creation-create', [
|
||||
h('h2', Messages.creation_createTitle)
|
||||
])).appendTo($creation);
|
||||
// Pick a template?
|
||||
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {
|
||||
if (!data) { return; }
|
||||
var $templateButton = $('<button>').text(Messages.creation_createFromTemplate)
|
||||
.appendTo($create);
|
||||
|
||||
var pickerCfg = {
|
||||
types: [type],
|
||||
where: ['template'],
|
||||
hidden: true
|
||||
};
|
||||
common.openFilePicker(pickerCfg);
|
||||
|
||||
$templateButton.click(function () {
|
||||
// Show the template picker
|
||||
delete pickerCfg.hidden;
|
||||
common.openFilePicker(pickerCfg);
|
||||
var first = true; // We can only pick a template once (for a new document)
|
||||
var fileDialogCfg = {
|
||||
onSelect: function (data) {
|
||||
if (data.type === type && first) {
|
||||
create(data.href);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
common.initFilePicker(fileDialogCfg);
|
||||
});
|
||||
});
|
||||
|
||||
var $button = $('<button>').text(Messages.creation_createFromScratch).appendTo($create);
|
||||
var $button = $('<button>').text(Messages.creation_create).appendTo($create);
|
||||
$button.addClass('cp-creation-button-selected');
|
||||
$button.click(function () {
|
||||
create();
|
||||
});
|
||||
|
||||
// Settings button
|
||||
var origin = common.getMetadataMgr().getPrivateData().origin;
|
||||
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide();
|
||||
// Save settings button
|
||||
/*var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide();
|
||||
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide();
|
||||
var okTo;
|
||||
var $saveButton = $('<button>').text(Messages.creation_saveSettings).click(function () {
|
||||
|
@ -2025,35 +2103,29 @@ define([
|
|||
}, 5000);
|
||||
});
|
||||
});
|
||||
$(h('div.cp-creation-settings', [
|
||||
$saveButton[0],
|
||||
h('br'),
|
||||
h('a', {
|
||||
href: origin + '/settings/#creation',
|
||||
target: '_blank'
|
||||
}, Messages.creation_settings),
|
||||
$ok[0],
|
||||
$spinner[0]
|
||||
])).appendTo($creation);
|
||||
|
||||
var selected = 0;
|
||||
var next = function () {
|
||||
selected = ++selected % $creation.find('button').length;
|
||||
$creation.find('button').removeClass('cp-creation-button-selected');
|
||||
$($creation.find('button').get(selected)).addClass('cp-creation-button-selected');
|
||||
};
|
||||
var settings = h('div.cp-creation-settings', [
|
||||
h('button.cp-creation-settings-save', Messages.creation_saveSettings),
|
||||
$ok[0],
|
||||
$spinner[0],
|
||||
h('span.cp-filler'),
|
||||
createHelper('/settings/#creation')
|
||||
//h('a', {
|
||||
// href: origin + '/settings/#creation',
|
||||
// target: '_blank'
|
||||
//}, Messages.creation_settings),
|
||||
]);*/
|
||||
|
||||
|
||||
$creation.keydown(function (e) {
|
||||
if (e.which === 9) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
next();
|
||||
next(e.shiftKey);
|
||||
return;
|
||||
}
|
||||
if (e.which === 13) {
|
||||
if ($creation.find('.cp-creation-button-selected').length === 1) {
|
||||
$creation.find('.cp-creation-button-selected').click();
|
||||
}
|
||||
$button.click();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -75,13 +75,7 @@ define([
|
|||
cb();
|
||||
});
|
||||
};
|
||||
// Settings and drive
|
||||
common.getUserObject = function (cb) {
|
||||
postMessage("GET", [], function (obj) {
|
||||
cb(obj);
|
||||
});
|
||||
};
|
||||
// Settings and auth
|
||||
// Settings and drive and auth
|
||||
common.getUserObject = function (cb) {
|
||||
postMessage("GET", [], function (obj) {
|
||||
cb(obj);
|
||||
|
@ -94,6 +88,10 @@ define([
|
|||
};
|
||||
postMessage("MIGRATE_ANON_DRIVE", data, cb);
|
||||
};
|
||||
// Settings
|
||||
common.deleteAccount = function (cb) {
|
||||
postMessage("DELETE_ACCOUNT", null, cb);
|
||||
};
|
||||
// Drive
|
||||
common.userObjectCommand = function (data, cb) {
|
||||
postMessage("DRIVE_USEROBJECT", data, cb);
|
||||
|
@ -468,6 +466,16 @@ define([
|
|||
cb(void 0, list);
|
||||
});
|
||||
};
|
||||
// Get a template href from its id
|
||||
common.getPadData = function (id, cb) {
|
||||
postMessage("GET_PAD_DATA", id, function (data) {
|
||||
cb(void 0, data);
|
||||
});
|
||||
};
|
||||
// Set initial path when creating a pad from pad creation screen
|
||||
common.setInitialPath = function (path) {
|
||||
postMessage("SET_INITIAL_PATH", path);
|
||||
};
|
||||
|
||||
// Messaging (manage friends from the userlist)
|
||||
common.inviteFromUserlist = function (netfluxId, cb) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
define([
|
||||
'json.sortify',
|
||||
'/common/userObject.js',
|
||||
'/common/migrate-user-object.js',
|
||||
'/common/common-hash.js',
|
||||
|
@ -15,7 +16,7 @@ define([
|
|||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
|
||||
'/bower_components/chainpad/chainpad.dist.js',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
], function (UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
|
||||
], function (Sortify, UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
|
||||
CpNfWorker, NetConfig, AppConfig,
|
||||
Crypto, ChainPad, Listmap) {
|
||||
var Store = {};
|
||||
|
@ -69,8 +70,16 @@ define([
|
|||
var userChannel = userParsedHash && userParsedHash.channel;
|
||||
if (!userChannel) { return null; }
|
||||
|
||||
var list = store.userObject.getFiles([store.userObject.FILES_DATA]).map(function (id) {
|
||||
return Hash.hrefToHexChannelId(store.userObject.getFileData(id).href);
|
||||
// Get the list of pads' channel ID in your drive
|
||||
// This list is filtered so that it doesn't include pad owned by other users (you should
|
||||
// not pin these pads)
|
||||
var files = store.userObject.getFiles([store.userObject.FILES_DATA]);
|
||||
var edPublic = store.proxy.edPublic;
|
||||
var list = files.map(function (id) {
|
||||
var d = store.userObject.getFileData(id);
|
||||
if (d.owners && d.owners.length && edPublic &&
|
||||
d.owners.indexOf(edPublic) === -1) { return; }
|
||||
return Hash.hrefToHexChannelId(d.href);
|
||||
})
|
||||
.filter(function (x) { return x; });
|
||||
|
||||
|
@ -372,7 +381,7 @@ define([
|
|||
var metadata = {
|
||||
// "user" is shared with everybody via the userlist
|
||||
user: {
|
||||
name: store.proxy[Constants.displayNameKey],
|
||||
name: store.proxy[Constants.displayNameKey] || "",
|
||||
uid: store.proxy.uid,
|
||||
avatar: Util.find(store.proxy, ['profile', 'avatar']),
|
||||
profile: Util.find(store.proxy, ['profile', 'view']),
|
||||
|
@ -412,6 +421,23 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
Store.deleteAccount = function (data, cb) {
|
||||
var toSign = {
|
||||
intent: 'Please delete my account.'
|
||||
};
|
||||
var secret = Hash.getSecrets('drive', storeHash);
|
||||
toSign.drive = secret.channel;
|
||||
toSign.edPublic = store.proxy.edPublic;
|
||||
var signKey = Crypto.Nacl.util.decodeBase64(secret.keys.signKey);
|
||||
console.log(Sortify(toSign));
|
||||
var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey);
|
||||
var proofTxt = Crypto.Nacl.util.encodeBase64(proof);
|
||||
cb({
|
||||
proof: proofTxt,
|
||||
toSign: JSON.parse(Sortify(toSign))
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* add a "What is CryptPad?" pad in the drive
|
||||
* data
|
||||
|
@ -678,6 +704,13 @@ define([
|
|||
});
|
||||
cb(list);
|
||||
};
|
||||
Store.getPadData = function (id, cb) {
|
||||
cb(store.userObject.getFileData(id));
|
||||
};
|
||||
Store.setInitialPath = function (path) {
|
||||
if (!store.data) { return; }
|
||||
store.data.initialPath = path;
|
||||
};
|
||||
|
||||
// Messaging (manage friends from the userlist)
|
||||
var getMessagingCfg = function () {
|
||||
|
|
|
@ -117,6 +117,12 @@ define([
|
|||
case 'GET_SECURE_FILES_LIST': {
|
||||
Store.getSecureFilesList(data, cb); break;
|
||||
}
|
||||
case 'GET_PAD_DATA': {
|
||||
Store.getPadData(data, cb); break;
|
||||
}
|
||||
case 'SET_INITIAL_PATH': {
|
||||
Store.setInitialPath(data); break;
|
||||
}
|
||||
case 'GET_STRONGER_HASH': {
|
||||
Store.getStrongerHash(data, cb); break;
|
||||
}
|
||||
|
@ -166,7 +172,10 @@ define([
|
|||
case 'DRIVE_USEROBJECT': {
|
||||
Store.userObjectCommand(data, cb); break;
|
||||
}
|
||||
|
||||
// Settings
|
||||
case 'DELETE_ACCOUNT': {
|
||||
Store.deleteAccount(data, cb); break;
|
||||
}
|
||||
case 'IS_NEW_CHANNEL': {
|
||||
Store.isNewChannel(data, cb); break;
|
||||
}
|
||||
|
|
|
@ -469,6 +469,35 @@ define([
|
|||
cb(templates.length > 0);
|
||||
});
|
||||
});
|
||||
var getKey = function (href) {
|
||||
var parsed = Utils.Hash.parsePadUrl(href);
|
||||
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
|
||||
};
|
||||
sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) {
|
||||
Cryptpad.getSecureFilesList({
|
||||
types: [type],
|
||||
where: ['template']
|
||||
}, function (err, data) {
|
||||
// NOTE: Never return data directly!
|
||||
if (err) { return void cb({error: err}); }
|
||||
|
||||
var res = [];
|
||||
nThen(function (waitFor) {
|
||||
Object.keys(data).map(function (el) {
|
||||
var k = getKey(data[el].href);
|
||||
Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) {
|
||||
res.push({
|
||||
id: el,
|
||||
name: data[el].filename || data[el].title || '?',
|
||||
thumbnail: thumb
|
||||
});
|
||||
}));
|
||||
});
|
||||
}).nThen(function () {
|
||||
cb({data: res});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sframeChan.on('EV_GOTO_URL', function (url) {
|
||||
if (url) {
|
||||
|
@ -584,7 +613,7 @@ define([
|
|||
var replaceHash = function (hash) {
|
||||
if (window.history && window.history.replaceState) {
|
||||
if (!/^#/.test(hash)) { hash = '#' + hash; }
|
||||
void window.history.replaceState({}, window.document.title, hash);
|
||||
window.history.replaceState({}, window.document.title, hash);
|
||||
if (typeof(window.onhashchange) === 'function') {
|
||||
window.onhashchange();
|
||||
}
|
||||
|
@ -646,19 +675,31 @@ define([
|
|||
if (data.expire) {
|
||||
rtConfig.expire = data.expire;
|
||||
}
|
||||
if (data.template) {
|
||||
// Pass rtConfig to useTemplate because Cryptput will create the file and
|
||||
// we need to have the owners and expiration time in the first line on the
|
||||
// server
|
||||
Cryptpad.useTemplate(data.template, Cryptget, function () {
|
||||
startRealtime();
|
||||
cb();
|
||||
}, rtConfig);
|
||||
return;
|
||||
}
|
||||
// Start realtime outside the iframe and callback
|
||||
startRealtime(rtConfig);
|
||||
cb();
|
||||
nThen(function(waitFor) {
|
||||
if (data.templateId) {
|
||||
if (data.templateId === -1) {
|
||||
Cryptpad.setInitialPath(['template']);
|
||||
return;
|
||||
}
|
||||
Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
|
||||
data.template = d.href;
|
||||
}));
|
||||
}
|
||||
}).nThen(function () {
|
||||
if (data.template) {
|
||||
// Pass rtConfig to useTemplate because Cryptput will create the file and
|
||||
// we need to have the owners and expiration time in the first line on the
|
||||
// server
|
||||
Cryptpad.useTemplate(data.template, Cryptget, function () {
|
||||
startRealtime();
|
||||
cb();
|
||||
}, rtConfig);
|
||||
return;
|
||||
}
|
||||
// Start realtime outside the iframe and callback
|
||||
startRealtime(rtConfig);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
sframeChan.ready();
|
||||
|
|
|
@ -97,6 +97,7 @@ define([
|
|||
|
||||
// Thumb
|
||||
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);
|
||||
funcs.addThumbnail = Thumb.addThumbnail;
|
||||
|
||||
// History
|
||||
funcs.getHistory = callWithCommon(History.create);
|
||||
|
@ -196,7 +197,8 @@ define([
|
|||
ctx.sframeChan.query("Q_CREATE_PAD", {
|
||||
owned: cfg.owned,
|
||||
expire: cfg.expire,
|
||||
template: cfg.template
|
||||
template: cfg.template,
|
||||
templateId: cfg.templateId
|
||||
}, cb);
|
||||
};
|
||||
|
||||
|
|
|
@ -193,6 +193,7 @@ define({
|
|||
'Q_SETTINGS_LOGOUT': true,
|
||||
// Import pads from this computer's anon session into the current user account
|
||||
'Q_SETTINGS_IMPORT_LOCAL': true,
|
||||
'Q_SETTINGS_DELETE_ACCOUNT': true,
|
||||
|
||||
// Store the language selected in the iframe into localStorage outside
|
||||
'Q_LANGUAGE_SET': true,
|
||||
|
@ -220,6 +221,8 @@ define({
|
|||
|
||||
// Pad creation screen: create a pad with the selected attributes (owned, expire)
|
||||
'Q_CREATE_PAD': true,
|
||||
// Get the available templates
|
||||
'Q_CREATE_TEMPLATES': true,
|
||||
|
||||
// This is for sending data out of the iframe when we are in testing mode
|
||||
// The exact protocol is defined in common/test.js
|
||||
|
|
|
@ -400,7 +400,9 @@ define([
|
|||
});
|
||||
show();
|
||||
Common.getAttribute(['toolbar', 'userlist-drawer'], function (err, val) {
|
||||
if (val === false || $(window).height() < 800) { return void hide(); }
|
||||
if (val === false || ($(window).height() < 800 && $(window).width() < 800)) {
|
||||
return void hide();
|
||||
}
|
||||
show();
|
||||
});
|
||||
|
||||
|
@ -885,7 +887,6 @@ define([
|
|||
var oldUserData;
|
||||
if (!config.metadataMgr) { return; }
|
||||
var metadataMgr = config.metadataMgr;
|
||||
var userNetfluxId = metadataMgr.getNetfluxId();
|
||||
var notify = function(type, name, oldname) {
|
||||
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
|
||||
if (typeof name === "undefined") { return; }
|
||||
|
@ -929,6 +930,7 @@ define([
|
|||
metadataMgr.onChange(function () {
|
||||
var newdata = metadataMgr.getMetadata().users;
|
||||
var netfluxIds = Object.keys(newdata);
|
||||
var userNetfluxId = metadataMgr.getNetfluxId();
|
||||
// Notify for disconnected users
|
||||
if (typeof oldUserData !== "undefined") {
|
||||
for (var u in oldUserData) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/contacts/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="/contacts/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
|
@ -111,11 +111,9 @@ table#cp-app-poll-table {
|
|||
}
|
||||
#cp-app-poll-table-container {
|
||||
position: relative;
|
||||
padding: 30px 0;
|
||||
width: ~"calc(100% - 30px)";
|
||||
margin: 20px;
|
||||
}
|
||||
#cp-app-poll-table-container button {
|
||||
//display: none;
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
@ -148,8 +146,8 @@ table#cp-app-poll-table {
|
|||
#cp-app-poll-table-scroll {
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
margin-left: ~"calc(25% + 30px)";
|
||||
max-width: ~"calc(75% - 30px - 100px - 100px)";
|
||||
margin-left: 25%;
|
||||
max-width: ~"calc(75% - 100px - 100px)";
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -227,7 +225,6 @@ table {
|
|||
margin: 20px;
|
||||
}
|
||||
tbody {
|
||||
//border: 1px solid @poll-border-color;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
@ -298,7 +295,7 @@ div.cp-app-poll-realtime {
|
|||
/* Options */
|
||||
td:first-child {
|
||||
position:absolute;
|
||||
left: 30px;
|
||||
left: 0;
|
||||
top: auto;
|
||||
width: 25%;
|
||||
}
|
||||
|
@ -454,7 +451,6 @@ div.cp-app-poll-realtime {
|
|||
&:nth-last-child(2) {
|
||||
border-right: 1px solid @poll-border-color;
|
||||
}
|
||||
//text-align: center;
|
||||
&.cp-app-poll-table-own {
|
||||
background: @poll-th-user-bg;
|
||||
.cp-app-poll-table-lock {
|
||||
|
@ -631,8 +627,42 @@ div.cp-app-poll-realtime {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
#cp-app-poll-table-scroll {
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
table {
|
||||
tr {
|
||||
td {
|
||||
&:first-child {
|
||||
position: unset;
|
||||
min-width: 100px;
|
||||
&:hover:not(:empty) {
|
||||
position: absolute;
|
||||
min-width: 100px;
|
||||
width: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
&:nth-last-child(2) {
|
||||
position: unset;
|
||||
}
|
||||
&:last-child {
|
||||
position: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#cp-app-poll-comments {
|
||||
min-width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -163,11 +163,10 @@ define([
|
|||
var sortColumns = function (order, firstcol) {
|
||||
var colsOrder = order.slice();
|
||||
// Never put at the first position an uncommitted column
|
||||
if (APP.proxy.content.colsOrder.indexOf(firstcol) === -1) { return colsOrder; }
|
||||
colsOrder.sort(function (a, b) {
|
||||
return (a === firstcol) ? -1 :
|
||||
((b === firstcol) ? 1 : 0);
|
||||
});
|
||||
var idx = APP.proxy.content.colsOrder.indexOf(firstcol);
|
||||
if (!firstcol || idx === -1) { return colsOrder; }
|
||||
colsOrder.splice(idx, 1);
|
||||
colsOrder.unshift(firstcol);
|
||||
return colsOrder;
|
||||
};
|
||||
|
||||
|
@ -618,7 +617,6 @@ define([
|
|||
// If readOnly, always put the app in published mode
|
||||
bool = true;
|
||||
}
|
||||
console.log(bool);
|
||||
$(APP.$mediaTagButton).toggle(!bool);
|
||||
setTablePublished(bool);
|
||||
/*['textarea'].forEach(function (sel) {
|
||||
|
@ -1106,6 +1104,25 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
var onError = function (info) {
|
||||
if (info && info.type) {
|
||||
if (info.type === 'CHAINPAD') {
|
||||
APP.unrecoverable = true;
|
||||
setEditable(false);
|
||||
APP.toolbar.errorState(true, info.error);
|
||||
var msg = Messages.chainpadError;
|
||||
UI.errorLoadingScreen(msg, true, true);
|
||||
console.error(info.error);
|
||||
return;
|
||||
}
|
||||
// Server error
|
||||
return void common.onServerError(info, APP.toolbar, function () {
|
||||
APP.unrecoverable = true;
|
||||
setEditable(false);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Manage disconnections because of network or error
|
||||
var onDisconnect = function (info) {
|
||||
if (APP.unrecoverable) { return; }
|
||||
|
@ -1251,7 +1268,6 @@ define([
|
|||
}).nThen(function (waitFor) {
|
||||
common.handleNewFile(waitFor);
|
||||
}).nThen(function (/* waitFor */) {
|
||||
console.log('here');
|
||||
Test.registerInner(common.getSframeChannel());
|
||||
var metadataMgr = common.getMetadataMgr();
|
||||
APP.locked = APP.readOnly = metadataMgr.getPrivateData().readOnly;
|
||||
|
@ -1318,7 +1334,8 @@ define([
|
|||
});
|
||||
})
|
||||
.on('disconnect', onDisconnect)
|
||||
.on('reconnect', onReconnect);
|
||||
.on('reconnect', onReconnect)
|
||||
.on('error', onError);
|
||||
});
|
||||
};
|
||||
main();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<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>
|
||||
<script async data-bootload="/profile/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
|
@ -138,5 +138,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-settings-delete-alert {
|
||||
pre { color: inherit; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/settings/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="/settings/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
|
@ -47,7 +47,8 @@ define([
|
|||
'cp-settings-logout-everywhere',
|
||||
'cp-settings-resettips',
|
||||
'cp-settings-thumbnails',
|
||||
'cp-settings-userfeedback'
|
||||
'cp-settings-userfeedback',
|
||||
'cp-settings-delete'
|
||||
],
|
||||
'creation': [
|
||||
'cp-settings-creation-owned',
|
||||
|
@ -317,6 +318,48 @@ define([
|
|||
return $div;
|
||||
};
|
||||
|
||||
create['delete'] = function () {
|
||||
var $div = $('<div>', { 'class': 'cp-settings-delete cp-sidebarlayout-element'});
|
||||
|
||||
$('<span>', {'class': 'label'}).text('DELETE ACCOUNT').appendTo($div); // XXX
|
||||
|
||||
$('<span>', {'class': 'cp-sidebarlayout-description'})
|
||||
.append('DELETE ACCOUNT DESCRIPTION').appendTo($div); // XXX
|
||||
|
||||
//var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
|
||||
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
|
||||
|
||||
var $button = $('<button>', {'id': 'cp-settings-delete', 'class': 'btn btn-primary'})
|
||||
.text('DELETE MY ACCOUNT PERMANENTLY').appendTo($div); // XXX
|
||||
|
||||
$button.click(function () {
|
||||
$spinner.show();
|
||||
sframeChan.query("Q_SETTINGS_DELETE_ACCOUNT", null, function (err, data) {
|
||||
var msg = h('div.cp-app-settings-delete-alert', [
|
||||
h('p', 'SEND US THE FOLLOWING DATA'),
|
||||
h('pre', JSON.stringify(data, 0, 2))
|
||||
]);
|
||||
UI.alert(msg);
|
||||
$spinner.hide();
|
||||
});
|
||||
// TODO
|
||||
/*
|
||||
UI.confirm("Are you sure?", function (yes) {
|
||||
// Logout everywhere
|
||||
// Disconnect other tabs
|
||||
// Remove owned pads
|
||||
// Remove owned drive
|
||||
// Remove pinstore
|
||||
// Alert: "Account deleted", press OK to be redirected to the home page
|
||||
$spinner.hide();
|
||||
});*/
|
||||
});
|
||||
|
||||
$spinner.hide().appendTo($div);
|
||||
|
||||
return $div;
|
||||
};
|
||||
|
||||
// Pad Creation settings
|
||||
|
||||
var setHTML = function (e, html) {
|
||||
|
|
|
@ -65,6 +65,9 @@ define([
|
|||
sframeChan.on('Q_SETTINGS_IMPORT_LOCAL', function (data, cb) {
|
||||
Cryptpad.mergeAnonDrive(cb);
|
||||
});
|
||||
sframeChan.on('Q_SETTINGS_DELETE_ACCOUNT', function (data, cb) {
|
||||
Cryptpad.deleteAccount(cb);
|
||||
});
|
||||
};
|
||||
var category;
|
||||
if (window.location.hash) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<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/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/worker/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="/worker/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue