Fossil

Check-in [7e48953c16]
Login

Check-in [7e48953c16]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:chat: reworked the drag/drop bits to take advantage of Firefox and Chrome already supporting drag/drop onto a file input element.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | chatroom-dev
Files: files | file ages | folders
SHA3-256: 7e48953c16df07d35c150ee1c3d14bfad7a79969d515f3f836e06132bc80521d
User & Date: stephan 2020-12-23 15:00:26.988
Context
2020-12-23
15:12
chat: improved visual notification of drag/drop into the file input selector. ... (check-in: d521007602 user: stephan tags: chatroom-dev)
15:00
chat: reworked the drag/drop bits to take advantage of Firefox and Chrome already supporting drag/drop onto a file input element. ... (check-in: 7e48953c16 user: stephan tags: chatroom-dev)
14:29
The "fossil scrub --verily" command deletes all chat history. ... (check-in: 7779535f04 user: drh tags: chatroom-dev)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
82
83
84
85
86
87
88

89
90

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116



117
118

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
  @   flex: 5 1 auto;
  @ }
  @ #chat-input-file {
  @   display: flex;
  @   flex-direction: row;
  @   align-items: center;
  @ }

  @ #chat-input-file > input[type=file] {
  @   align-self: flex-start;

  @   flex: 1 1 auto;
  @ }
  @ #chat-input-file > input {
  @   flex: 1 0 auto;
  @ }
  @ #chat-input-file > *:nth-child(1) { margin-right: 0.5em; }
  @ #chat-input-file > *:nth-child(2) { margin-left: 0.5em; }
  @ .chat-timestamp {
  @    font-family: monospace;
  @    font-size: 0.8em;
  @    white-space: pre;
  @    text-align: left;
  @    opacity: 0.8;
  @ }
  @ #chat-drop-zone {
  @   box-sizing: content-box;
  @   background-color: #e0e0e0;
  @   flex: 1 1 auto;
  @   padding: 0.5em 1em;
  @   border: 1px solid #808080;
  @   border-radius: 0.25em;
  @ }
  @ #chat-drop-zone.dragover {
  @   border: 1px dashed green;
  @ }
  @ #chat-drop-details {



  @   white-space: pre;
  @   font-family: monospace;

  @ }
  @ </style>
  @ <form accept-encoding="utf-8" id="chat-form">
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line'>
  @     <input type="text" name="msg" id="sbox" \
  @      placeholder="Type message here.">
  @     <input type="submit" value="Send">
  @   </div>
  @   <div id='chat-input-file'>
  @     <input type="file" name="file">
  @     <div id="chat-drop-zone">
  @        <div id="chat-drop-details"></div>
  @      </div>
  @   </div>
  @ </div>
  @ </form>
  @ <hr>

  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>







>


>





<
<







<
<
<
<
<
<
<
<
|



>
>
>


>











<
|
<







82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97


98
99
100
101
102
103
104








105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

126

127
128
129
130
131
132
133
  @   flex: 5 1 auto;
  @ }
  @ #chat-input-file {
  @   display: flex;
  @   flex-direction: row;
  @   align-items: center;
  @ }
  @ #chat-input-file > .help-buttonlet,
  @ #chat-input-file > input[type=file] {
  @   align-self: flex-start;
  @   margin-right: 0.5em;
  @   flex: 1 1 auto;
  @ }
  @ #chat-input-file > input {
  @   flex: 1 0 auto;
  @ }


  @ .chat-timestamp {
  @    font-family: monospace;
  @    font-size: 0.8em;
  @    white-space: pre;
  @    text-align: left;
  @    opacity: 0.8;
  @ }








  @ .dragover {
  @   border: 1px dashed green;
  @ }
  @ #chat-drop-details {
  @   flex: 0 1 auto;
  @   padding: 0.5em 1em;
  @   margin-left: 0.5em;
  @   white-space: pre;
  @   font-family: monospace;
  @   max-width: 50%%;
  @ }
  @ </style>
  @ <form accept-encoding="utf-8" id="chat-form">
  @ <div id='chat-input-area'>
  @   <div id='chat-input-line'>
  @     <input type="text" name="msg" id="sbox" \
  @      placeholder="Type message here.">
  @     <input type="submit" value="Send">
  @   </div>
  @   <div id='chat-input-file'>
  @     <input type="file" name="file">

  @     <div id="chat-drop-details"></div>

  @   </div>
  @ </div>
  @ </form>
  @ <hr>

  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>
Changes to src/chat.js.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
(function(){
  const form = document.querySelector('#chat-form');
  let mxMsg = 0;
  const F = window.fossil, D = F.dom;
  const _me = F.user.name;
  /* State for paste and drag/drop */
  const BlobXferState = {
    dropZone: document.querySelector('#chat-drop-zone'),
    dropDetails: document.querySelector('#chat-drop-details'),
    blob: undefined
  };
  /** Updates the paste/drop zone with details of the pasted/dropped
      data. */
  const updateDropZoneContent = function(blob){
    const bx = BlobXferState, dd = bx.dropDetails;
    bx.blob = blob;
    D.clearElement(dd);
    if(!blob) return;



    D.append(dd, "Name: ", blob.name,
             D.br(), "Size: ",blob.size);
    if(blob.type && blob.type.startsWith("image/")){
      const img = D.img();
      D.append(dd, D.br(), img);
      const reader = new FileReader();
      reader.onload = (e)=>img.setAttribute('src', e.target.result);
      reader.readAsDataURL(blob);
    }
    const btn = D.button("Cancel");
    D.append(dd, D.br(), btn);
    btn.addEventListener('click', ()=>updateDropZoneContent(), false);
  };
  ////////////////////////////////////////////////////////////
  // File drag/drop.
  // Adapted from: https://stackoverflow.com/a/58677161
  const dropHighlight = BlobXferState.dropZone /* target zone */;
  const dropEvents = {
    drop: function(ev){
      ev.preventDefault();
      D.removeClass(dropHighlight, 'dragover');
      const file = ev.dataTransfer.files[0];
      if(file) {
        updateDropZoneContent(file);
      }
    },
    dragenter: function(ev){
      ev.preventDefault();
      ev.dataTransfer.dropEffect = "copy";
      D.addClass(dropHighlight, 'dragover');
    },
    dragover: function(ev){
      ev.preventDefault();
    },
    dragend: function(ev){
      ev.preventDefault();
    },
    dragleave: function(ev){
      ev.preventDefault();
      D.removeClass(dropHighlight, 'dragover');
    }
  };
  /*
    The idea here is to accept drops at multiple points or, ideally,
    document.body, and apply them to P.e.taContent, but the precise
    combination of event handling needed to pull this off is eluding
    me.
  */
  [BlobXferState.dropZone
   /* ideally we'd link only to document.body, but the events seem to
      get out of whack, with dropleave being triggered at unexpected
      points. */
  ].forEach(function(e){
    Object.keys(dropEvents).forEach(
      (k)=>e.addEventListener(k, dropEvents[k], true)
    );
  });

  form.addEventListener('submit',(e)=>{
    e.preventDefault();
    const fd = new FormData(form);
    if(BlobXferState.blob/*replace file content with this*/){
      fd.set("file", BlobXferState.blob);







<









|
>
>
>













<
<
<
<
<
|
<
<
|
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







1
2
3
4
5
6
7

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33





34


35

36
































37
38
39
40
41
42
43
(function(){
  const form = document.querySelector('#chat-form');
  let mxMsg = 0;
  const F = window.fossil, D = F.dom;
  const _me = F.user.name;
  /* State for paste and drag/drop */
  const BlobXferState = {

    dropDetails: document.querySelector('#chat-drop-details'),
    blob: undefined
  };
  /** Updates the paste/drop zone with details of the pasted/dropped
      data. */
  const updateDropZoneContent = function(blob){
    const bx = BlobXferState, dd = bx.dropDetails;
    bx.blob = blob;
    D.clearElement(dd);
    if(!blob){
      form.file.value = '';
      return;
    }
    D.append(dd, "Name: ", blob.name,
             D.br(), "Size: ",blob.size);
    if(blob.type && blob.type.startsWith("image/")){
      const img = D.img();
      D.append(dd, D.br(), img);
      const reader = new FileReader();
      reader.onload = (e)=>img.setAttribute('src', e.target.result);
      reader.readAsDataURL(blob);
    }
    const btn = D.button("Cancel");
    D.append(dd, D.br(), btn);
    btn.addEventListener('click', ()=>updateDropZoneContent(), false);
  };





  form.file.addEventListener('change', function(ev){


    //console.debug("this =",this);

    updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
































  });

  form.addEventListener('submit',(e)=>{
    e.preventDefault();
    const fd = new FormData(form);
    if(BlobXferState.blob/*replace file content with this*/){
      fd.set("file", BlobXferState.blob);
104
105
106
107
108
109
110
111
112
113
114
115
116
117

118
119
120
121
122
123
124
      updateDropZoneContent(items[0].getAsFile());
    }else if('string'===item.kind){
      item.getAsString((v)=>form.msg.value = v);
    }
  };
  if(true){/* Add help button for drag/drop/paste zone */
    const help = D.div();
    BlobXferState.dropDetails.parentNode.insertBefore(
      help,BlobXferState.dropDetails
    );
    F.helpButtonlets.create(
      help,
      "Drag/drop a file into this spot, or paste an image "+
        "from the clipboard if supported by your environment."

    );
  }

  /* Injects element e as a new row in the chat, at the top of the list */
  const injectMessage = function f(e){
    if(!f.injectPoint){
      f.injectPoint = document.querySelector('#message-inject-point');







|
<
<


|
|
>







66
67
68
69
70
71
72
73


74
75
76
77
78
79
80
81
82
83
84
85
      updateDropZoneContent(items[0].getAsFile());
    }else if('string'===item.kind){
      item.getAsString((v)=>form.msg.value = v);
    }
  };
  if(true){/* Add help button for drag/drop/paste zone */
    const help = D.div();
    form.file.parentNode.insertBefore(help, form.file);


    F.helpButtonlets.create(
      help,
      "Select a file to upload, drag/drop a file into this spot, ",
      "or paste an image from the clipboard if supported by ",
      "your environment."
    );
  }

  /* Injects element e as a new row in the chat, at the top of the list */
  const injectMessage = function f(e){
    if(!f.injectPoint){
      f.injectPoint = document.querySelector('#message-inject-point');