mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Feature: Share links (#3996)
* Implement share links Basic implementation of share links Make certain share link fields not editable, automatically grant permissions on migrate Updated styling, error messages from expired / deleted links frontend code linting, reversable sharelink migration testing coverage Update translation strings No links message * Consolidate file response methods * improvements to share links on mobile devices * Refactor share links file_version * Add docs for share links * Apply suggestions from code review * When filtering share links, use the timezone aware now() * Removes extra call to setup directories for usage in testing * FIx copied badge display on some browsers * Move copy to ngx-clipboard library --------- Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
This commit is contained in:
		| @@ -319,7 +319,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">55</context> | ||||
|           <context context-type="linenumber">65</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1241348629231510663" datatype="html"> | ||||
| @@ -1073,7 +1073,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|           <context context-type="linenumber">208</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context> | ||||
| @@ -1142,7 +1142,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">182</context> | ||||
|           <context context-type="linenumber">192</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -1549,6 +1549,10 @@ | ||||
|           <context context-type="sourcefile">src/app/components/common/permissions-select/permissions-select.component.html</context> | ||||
|           <context context-type="linenumber">9</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">33</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">11</context> | ||||
| @@ -2240,6 +2244,135 @@ | ||||
|           <context context-type="linenumber">20</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="686374493515618129" datatype="html"> | ||||
|         <source>Share Links</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">6</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">25</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6617773613987957957" datatype="html"> | ||||
|         <source> No existing links </source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">10,12</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4323470180912194028" datatype="html"> | ||||
|         <source>Copy</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7419704019640008953" datatype="html"> | ||||
|         <source>Share</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">28</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5392341774767336507" datatype="html"> | ||||
|         <source>Copied!</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">36</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6811921365829755679" datatype="html"> | ||||
|         <source>Share archive version</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">42</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5674286808255988565" datatype="html"> | ||||
|         <source>Create</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.html</context> | ||||
|           <context context-type="linenumber">55</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4776429682428363094" datatype="html"> | ||||
|         <source>1 day</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">18</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">85</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8542568275115626925" datatype="html"> | ||||
|         <source>7 days</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">19</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7152095234138763013" datatype="html"> | ||||
|         <source>30 days</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">20</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8372007266188249803" datatype="html"> | ||||
|         <source>Never</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">21</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3429210839568770054" datatype="html"> | ||||
|         <source>Error retrieving links</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">69</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3242255798983858463" datatype="html"> | ||||
|         <source><x id="PH" equiv-text="days"/> days</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">85</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2897042887615940599" datatype="html"> | ||||
|         <source>Error deleting link</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">112</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8400747326190565173" datatype="html"> | ||||
|         <source>Error creating link</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/common/share-links-dropdown/share-links-dropdown.component.ts</context> | ||||
|           <context context-type="linenumber">140</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5611592591303869712" datatype="html"> | ||||
|         <source>Status</source> | ||||
|         <context-group purpose="location"> | ||||
| @@ -2310,7 +2443,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">75</context> | ||||
|           <context context-type="linenumber">85</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> | ||||
| @@ -2340,7 +2473,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">19</context> | ||||
|           <context context-type="linenumber">18</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -2538,14 +2671,65 @@ | ||||
|         <source>Download original</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">25</context> | ||||
|           <context context-type="linenumber">24</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3193976279273491157" datatype="html"> | ||||
|         <source>Actions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">34</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
|           <context context-type="linenumber">86</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">221</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">259</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">296</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">347</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">382</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context> | ||||
|           <context context-type="linenumber">44</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8312409092917397847" datatype="html"> | ||||
|         <source>Redo OCR</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">34</context> | ||||
|           <context context-type="linenumber">40</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -2556,7 +2740,7 @@ | ||||
|         <source>More like this</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">40</context> | ||||
|           <context context-type="linenumber">46</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context> | ||||
| @@ -2567,7 +2751,7 @@ | ||||
|         <source>Close</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">43</context> | ||||
|           <context context-type="linenumber">53</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context> | ||||
| @@ -2578,35 +2762,35 @@ | ||||
|         <source>Previous</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">50</context> | ||||
|           <context context-type="linenumber">60</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5028777105388019087" datatype="html"> | ||||
|         <source>Details</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">72</context> | ||||
|           <context context-type="linenumber">82</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1379170675585571971" datatype="html"> | ||||
|         <source>Archive serial number</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">76</context> | ||||
|           <context context-type="linenumber">86</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5114742157723900905" datatype="html"> | ||||
|         <source>Date created</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">77</context> | ||||
|           <context context-type="linenumber">87</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2691296884221415710" datatype="html"> | ||||
|         <source>Correspondent</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">79</context> | ||||
|           <context context-type="linenumber">89</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -2629,7 +2813,7 @@ | ||||
|         <source>Document type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">81</context> | ||||
|           <context context-type="linenumber">91</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -2652,7 +2836,7 @@ | ||||
|         <source>Storage path</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">83</context> | ||||
|           <context context-type="linenumber">93</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -2671,21 +2855,21 @@ | ||||
|         <source>Default</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">84</context> | ||||
|           <context context-type="linenumber">94</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6205355627445317276" datatype="html"> | ||||
|         <source>Content</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">91</context> | ||||
|           <context context-type="linenumber">101</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="218403386307979629" datatype="html"> | ||||
|         <source>Metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">100</context> | ||||
|           <context context-type="linenumber">110</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts</context> | ||||
| @@ -2696,173 +2880,173 @@ | ||||
|         <source>Date modified</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">106</context> | ||||
|           <context context-type="linenumber">116</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6392918669949841614" datatype="html"> | ||||
|         <source>Date added</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">110</context> | ||||
|           <context context-type="linenumber">120</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="146828917013192897" datatype="html"> | ||||
|         <source>Media filename</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">114</context> | ||||
|           <context context-type="linenumber">124</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4500855521601039868" datatype="html"> | ||||
|         <source>Original filename</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">118</context> | ||||
|           <context context-type="linenumber">128</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7985558498848210210" datatype="html"> | ||||
|         <source>Original MD5 checksum</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">122</context> | ||||
|           <context context-type="linenumber">132</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5888243105821763422" datatype="html"> | ||||
|         <source>Original file size</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">126</context> | ||||
|           <context context-type="linenumber">136</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2696647325713149563" datatype="html"> | ||||
|         <source>Original mime type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">130</context> | ||||
|           <context context-type="linenumber">140</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="342875990758166588" datatype="html"> | ||||
|         <source>Archive MD5 checksum</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">134</context> | ||||
|           <context context-type="linenumber">144</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6033581412811562084" datatype="html"> | ||||
|         <source>Archive file size</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">138</context> | ||||
|           <context context-type="linenumber">148</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6992781481378431874" datatype="html"> | ||||
|         <source>Original document metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">144</context> | ||||
|           <context context-type="linenumber">154</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2846565152091361585" datatype="html"> | ||||
|         <source>Archived document metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">145</context> | ||||
|           <context context-type="linenumber">155</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1295614462098694869" datatype="html"> | ||||
|         <source>Preview</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">151</context> | ||||
|           <context context-type="linenumber">161</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8191371354890763172" datatype="html"> | ||||
|         <source>Enter Password</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">167</context> | ||||
|           <context context-type="linenumber">177</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">218</context> | ||||
|           <context context-type="linenumber">228</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8460995830263484763" datatype="html"> | ||||
|         <source>Notes <x id="START_TAG_SPAN" ctype="x-span" equiv-text="<span *ngIf="document?.notes.length" class="badge text-bg-secondary ms-1">"/><x id="INTERPOLATION" equiv-text="ngth}}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="</a>"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">175,176</context> | ||||
|           <context context-type="linenumber">185,186</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3823219296477075982" datatype="html"> | ||||
|         <source>Discard</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">194</context> | ||||
|           <context context-type="linenumber">204</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5129524307369213584" datatype="html"> | ||||
|         <source>Save & next</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">196</context> | ||||
|           <context context-type="linenumber">206</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4910102545766233758" datatype="html"> | ||||
|         <source>Save & close</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> | ||||
|           <context context-type="linenumber">197</context> | ||||
|           <context context-type="linenumber">207</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2218903673684131427" datatype="html"> | ||||
|         <source>An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">252,254</context> | ||||
|           <context context-type="linenumber">253,255</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5758784066858623886" datatype="html"> | ||||
|         <source>Error retrieving metadata</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">397</context> | ||||
|           <context context-type="linenumber">398</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3456881259945295697" datatype="html"> | ||||
|         <source>Error retrieving suggestions.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">418</context> | ||||
|           <context context-type="linenumber">419</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8348337312757497317" datatype="html"> | ||||
|         <source>Document saved successfully.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">531</context> | ||||
|           <context context-type="linenumber">532</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">539</context> | ||||
|           <context context-type="linenumber">540</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="448882439049417053" datatype="html"> | ||||
|         <source>Error saving document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">543</context> | ||||
|           <context context-type="linenumber">544</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">584</context> | ||||
|           <context context-type="linenumber">585</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="9021887951960049161" datatype="html"> | ||||
|         <source>Confirm delete</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">610</context> | ||||
|           <context context-type="linenumber">611</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
| @@ -2873,35 +3057,35 @@ | ||||
|         <source>Do you really want to delete document "<x id="PH" equiv-text="this.document.title"/>"?</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">611</context> | ||||
|           <context context-type="linenumber">612</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6691075929777935948" datatype="html"> | ||||
|         <source>The files for this document will be deleted permanently. This operation cannot be undone.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">612</context> | ||||
|           <context context-type="linenumber">613</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="719892092227206532" datatype="html"> | ||||
|         <source>Delete document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">614</context> | ||||
|           <context context-type="linenumber">615</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7295637485862454066" datatype="html"> | ||||
|         <source>Error deleting document</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">633</context> | ||||
|           <context context-type="linenumber">634</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7362691899087997122" datatype="html"> | ||||
|         <source>Redo OCR confirm</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">653</context> | ||||
|           <context context-type="linenumber">654</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -2912,14 +3096,14 @@ | ||||
|         <source>This operation will permanently redo OCR for this document.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">654</context> | ||||
|           <context context-type="linenumber">655</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5641451190833696892" datatype="html"> | ||||
|         <source>This operation cannot be undone.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">655</context> | ||||
|           <context context-type="linenumber">656</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -2950,7 +3134,7 @@ | ||||
|         <source>Proceed</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">657</context> | ||||
|           <context context-type="linenumber">658</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context> | ||||
| @@ -2977,14 +3161,14 @@ | ||||
|         <source>Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">665</context> | ||||
|           <context context-type="linenumber">666</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4409560272830824468" datatype="html"> | ||||
|         <source>Error executing operation</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context> | ||||
|           <context context-type="linenumber">676</context> | ||||
|           <context context-type="linenumber">677</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6857598786757174736" datatype="html"> | ||||
| @@ -3045,53 +3229,6 @@ | ||||
|           <context context-type="linenumber">52</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3193976279273491157" datatype="html"> | ||||
|         <source>Actions</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
|           <context context-type="linenumber">86</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">23</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">221</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">259</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">296</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">347</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context> | ||||
|           <context context-type="linenumber">382</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/tasks/tasks.component.html</context> | ||||
|           <context context-type="linenumber">44</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1015374532025907183" datatype="html"> | ||||
|         <source>Include:</source> | ||||
|         <context-group purpose="location"> | ||||
| @@ -3945,25 +4082,6 @@ | ||||
|           <context context-type="linenumber">44</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5674286808255988565" datatype="html"> | ||||
|         <source>Create</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context> | ||||
|           <context context-type="linenumber">2</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4010735610815226758" datatype="html"> | ||||
|         <source>Filter by:</source> | ||||
|         <context-group purpose="location"> | ||||
|   | ||||
							
								
								
									
										29
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										29
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -24,6 +24,7 @@ | ||||
|         "file-saver": "^2.0.5", | ||||
|         "mime-names": "^1.0.0", | ||||
|         "ng2-pdf-viewer": "^10.0.0", | ||||
|         "ngx-clipboard": "^16.0.0", | ||||
|         "ngx-color": "^9.0.0", | ||||
|         "ngx-cookie-service": "^16.0.1", | ||||
|         "ngx-file-drop": "^16.0.0", | ||||
| @@ -14404,6 +14405,19 @@ | ||||
|         "pdfjs-dist": "~2.16.105" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ngx-clipboard": { | ||||
|       "version": "16.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-16.0.0.tgz", | ||||
|       "integrity": "sha512-rZ/Eo1PqiKMiyF8tdjhmUkoUu68f7OzBJ7YH1YFeh2RAaNrerTaW8XfFOzppSckjFQqA1fwGSYuTTJlDhDag5w==", | ||||
|       "dependencies": { | ||||
|         "ngx-window-token": ">=7.0.0", | ||||
|         "tslib": "^2.0.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@angular/common": ">=13.0.0", | ||||
|         "@angular/core": ">=13.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ngx-color": { | ||||
|       "version": "9.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz", | ||||
| @@ -14474,6 +14488,21 @@ | ||||
|         "@ng-bootstrap/ng-bootstrap": "^15.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/ngx-window-token": { | ||||
|       "version": "7.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-7.0.0.tgz", | ||||
|       "integrity": "sha512-5+XfRVSY7Dciu8xyCNMkOlH2UfwR9W2P1Pirz7caaZgOZDjFbL8aEO2stjfJJm2FFf1D6dlVHNzhLWGk9HGkqA==", | ||||
|       "dependencies": { | ||||
|         "tslib": "^2.0.0" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": "^14.20.0 || ^16.13.0 || >=18.10.0" | ||||
|       }, | ||||
|       "peerDependencies": { | ||||
|         "@angular/common": ">=13.0.0", | ||||
|         "@angular/core": ">=13.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/nice-napi": { | ||||
|       "version": "1.0.2", | ||||
|       "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", | ||||
|   | ||||
| @@ -26,6 +26,7 @@ | ||||
|     "file-saver": "^2.0.5", | ||||
|     "mime-names": "^1.0.0", | ||||
|     "ng2-pdf-viewer": "^10.0.0", | ||||
|     "ngx-clipboard": "^16.0.0", | ||||
|     "ngx-color": "^9.0.0", | ||||
|     "ngx-cookie-service": "^16.0.1", | ||||
|     "ngx-file-drop": "^16.0.0", | ||||
|   | ||||
| @@ -86,6 +86,7 @@ Object.defineProperty(navigator, 'clipboard', { | ||||
|     writeText: async () => {}, | ||||
|   }, | ||||
| }) | ||||
| Object.defineProperty(navigator, 'canShare', { value: () => true }) | ||||
| Object.defineProperty(window, 'ResizeObserver', { value: mock() }) | ||||
|  | ||||
| HTMLCanvasElement.prototype.getContext = < | ||||
|   | ||||
| @@ -94,6 +94,7 @@ import { PermissionsFilterDropdownComponent } from './components/common/permissi | ||||
| import { UsernamePipe } from './pipes/username.pipe' | ||||
| import { LogoComponent } from './components/common/logo/logo.component' | ||||
| import { IsNumberPipe } from './pipes/is-number.pipe' | ||||
| import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component' | ||||
|  | ||||
| import localeAf from '@angular/common/locales/af' | ||||
| import localeAr from '@angular/common/locales/ar' | ||||
| @@ -231,6 +232,7 @@ function initializeApp(settings: SettingsService) { | ||||
|     UsernamePipe, | ||||
|     LogoComponent, | ||||
|     IsNumberPipe, | ||||
|     ShareLinksDropdownComponent, | ||||
|   ], | ||||
|   imports: [ | ||||
|     BrowserModule, | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import { | ||||
|   ComponentFixture, | ||||
|   TestBed, | ||||
|   discardPeriodicTasks, | ||||
|   fakeAsync, | ||||
|   tick, | ||||
| } from '@angular/core/testing' | ||||
|   | ||||
| @@ -0,0 +1,61 @@ | ||||
| <div ngbDropdown> | ||||
|     <button class="btn btn-sm btn-outline-primary me-2" id="shareLinksDropdown" [disabled]="disabled" ngbDropdownToggle> | ||||
|       <svg class="toolbaricon" fill="currentColor"> | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#link" /> | ||||
|       </svg> | ||||
|       <div class="d-none d-sm-inline"> <ng-container i18n>Share Links</ng-container></div> | ||||
|     </button> | ||||
|     <div ngbDropdownMenu aria-labelledby="shareLinksDropdown" class="shadow share-links-dropdown"> | ||||
|       <ul class="list-group list-group-flush"> | ||||
|         <li *ngIf="!shareLinks || shareLinks.length === 0" class="list-group-item fst-italic small text-center text-secondary" i18n> | ||||
|           No existing links | ||||
|         </li> | ||||
|         <li class="list-group-item" *ngFor="let link of shareLinks"> | ||||
|           <div class="input-group input-group-sm w-100"> | ||||
|             <input type="text" class="form-control" aria-label="Share link" [value]="getShareUrl(link)" readonly> | ||||
|             <span *ngIf="link.expiration" class="input-group-text"> | ||||
|               {{ getDaysRemaining(link) }} | ||||
|             </span> | ||||
|             <button type="button" class="btn btn-sm btn-outline-primary" (click)="copy(link)"> | ||||
|               <svg class="buttonicon" fill="currentColor"> | ||||
|                 <use *ngIf="copied !== link.id" xlink:href="assets/bootstrap-icons.svg#clipboard-fill" /> | ||||
|                 <use *ngIf="copied === link.id" xlink:href="assets/bootstrap-icons.svg#clipboard-check-fill" /> | ||||
|               </svg><span class="visually-hidden" i18n>Copy</span> | ||||
|             </button> | ||||
|             <button *ngIf="canShare(link)" type="button" class="btn btn-sm btn-outline-primary" (click)="share(link)"> | ||||
|               <svg class="buttonicon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#box-arrow-up" /> | ||||
|               </svg><span class="visually-hidden" i18n>Share</span> | ||||
|             </button> | ||||
|             <button type="button" class="btn btn-sm btn-outline-danger" (click)="delete(link)"> | ||||
|                 <svg class="buttonicon" fill="currentColor"> | ||||
|                     <use xlink:href="assets/bootstrap-icons.svg#trash" /> | ||||
|                 </svg><span class="visually-hidden" i18n>Delete</span> | ||||
|             </button> | ||||
|           </div> | ||||
|           <span class="badge copied-badge bg-primary small fade ms-4 position-absolute top-50 translate-middle-y pe-none z-3" [class.show]="copied === link.id" i18n>Copied!</span> | ||||
|         </li> | ||||
|         <li class="list-group-item pt-3 pb-2"> | ||||
|           <div class="input-group input-group-sm w-100"> | ||||
|             <div class="form-check form-switch ms-auto"> | ||||
|               <input class="form-check-input" type="checkbox" role="switch" id="versionSwitch" [(ngModel)]="archiveVersion"> | ||||
|               <label class="form-check-label small" for="versionSwitch" i18n>Share archive version</label> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div class="input-group input-group-sm w-100 mt-2"> | ||||
|             <label class="input-group-text" for="addLink">Expires:</label> | ||||
|             <select class="form-select form-select-sm" [(ngModel)]="expirationDays"> | ||||
|               <option *ngFor="let option of EXPIRATION_OPTIONS" [ngValue]="option.value">{{ option.label }}</option> | ||||
|             </select> | ||||
|             <button class="btn btn-sm btn-outline-primary ms-auto" type="button" (click)="createLink()" [disabled]="loading"> | ||||
|               <div *ngIf="loading" class="spinner-border spinner-border-sm me-2" role="status"></div> | ||||
|               <svg *ngIf="!loading" class="buttonicon me-1" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#plus" /> | ||||
|               </svg> | ||||
|               <ng-container i18n>Create</ng-container> | ||||
|             </button> | ||||
|           </div> | ||||
|         </li> | ||||
|       </ul> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -0,0 +1,14 @@ | ||||
| .share-links-dropdown { | ||||
|     min-width: 350px; | ||||
|  | ||||
|     // correct position on mobile | ||||
|     @media (max-width: 575.98px) { | ||||
|         &.show { | ||||
|             margin-left: -175px !important; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| .copied-badge { | ||||
|     right: 7.5em; | ||||
| } | ||||
| @@ -0,0 +1,195 @@ | ||||
| import { | ||||
|   HttpTestingController, | ||||
|   HttpClientTestingModule, | ||||
| } from '@angular/common/http/testing' | ||||
| import { | ||||
|   ComponentFixture, | ||||
|   TestBed, | ||||
|   fakeAsync, | ||||
|   tick, | ||||
| } from '@angular/core/testing' | ||||
| import { FormsModule, ReactiveFormsModule } from '@angular/forms' | ||||
| import { of, throwError } from 'rxjs' | ||||
| import { | ||||
|   PaperlessFileVersion, | ||||
|   PaperlessShareLink, | ||||
| } from 'src/app/data/paperless-share-link' | ||||
| import { ShareLinkService } from 'src/app/services/rest/share-link.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { ShareLinksDropdownComponent } from './share-links-dropdown.component' | ||||
| import { ClipboardService } from 'ngx-clipboard' | ||||
|  | ||||
| describe('ShareLinksDropdownComponent', () => { | ||||
|   let component: ShareLinksDropdownComponent | ||||
|   let fixture: ComponentFixture<ShareLinksDropdownComponent> | ||||
|   let shareLinkService: ShareLinkService | ||||
|   let toastService: ToastService | ||||
|   let httpController: HttpTestingController | ||||
|   let clipboardService: ClipboardService | ||||
|  | ||||
|   beforeEach(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       declarations: [ShareLinksDropdownComponent], | ||||
|       imports: [HttpClientTestingModule, FormsModule, ReactiveFormsModule], | ||||
|     }) | ||||
|  | ||||
|     fixture = TestBed.createComponent(ShareLinksDropdownComponent) | ||||
|     shareLinkService = TestBed.inject(ShareLinkService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     httpController = TestBed.inject(HttpTestingController) | ||||
|     clipboardService = TestBed.inject(ClipboardService) | ||||
|  | ||||
|     component = fixture.componentInstance | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
|  | ||||
|   it('should support refresh to retrieve links', () => { | ||||
|     const getSpy = jest.spyOn(shareLinkService, 'getLinksForDocument') | ||||
|     component.documentId = 99 | ||||
|  | ||||
|     const now = new Date() | ||||
|     const expiration7days = new Date() | ||||
|     expiration7days.setDate(now.getDate() + 7) | ||||
|  | ||||
|     getSpy.mockReturnValue( | ||||
|       of([ | ||||
|         { | ||||
|           id: 1, | ||||
|           slug: '1234slug', | ||||
|           created: now.toISOString(), | ||||
|           document: 99, | ||||
|           file_version: PaperlessFileVersion.Archive, | ||||
|           expiration: expiration7days.toISOString(), | ||||
|         }, | ||||
|         { | ||||
|           id: 1, | ||||
|           slug: '1234slug', | ||||
|           created: now.toISOString(), | ||||
|           document: 99, | ||||
|           file_version: PaperlessFileVersion.Original, | ||||
|           expiration: null, | ||||
|         }, | ||||
|       ]) | ||||
|     ) | ||||
|  | ||||
|     component.refresh() | ||||
|     expect(getSpy).toHaveBeenCalled() | ||||
|  | ||||
|     fixture.detectChanges() | ||||
|  | ||||
|     expect(component.shareLinks).toHaveLength(2) | ||||
|   }) | ||||
|  | ||||
|   it('should show error on refresh if needed', () => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     jest | ||||
|       .spyOn(shareLinkService, 'getLinksForDocument') | ||||
|       .mockReturnValueOnce(throwError(() => new Error('Unable to get links'))) | ||||
|     component.documentId = 99 | ||||
|  | ||||
|     component.refresh() | ||||
|     fixture.detectChanges() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support link creation then refresh & copy url', fakeAsync(() => { | ||||
|     const createSpy = jest.spyOn(shareLinkService, 'createLinkForDocument') | ||||
|     component.documentId = 99 | ||||
|     component.expirationDays = 7 | ||||
|     component.archiveVersion = false | ||||
|  | ||||
|     const expiration = new Date() | ||||
|     expiration.setDate(expiration.getDate() + 7) | ||||
|  | ||||
|     const copySpy = jest.spyOn(clipboardService, 'copy') | ||||
|     const refreshSpy = jest.spyOn(component, 'refresh') | ||||
|  | ||||
|     component.createLink() | ||||
|     expect(createSpy).toHaveBeenCalledWith(99, 'original', expiration) | ||||
|  | ||||
|     httpController.expectOne(`${environment.apiBaseUrl}share_links/`).flush({ | ||||
|       id: 1, | ||||
|       slug: '1234slug', | ||||
|       document: 99, | ||||
|       expiration: expiration.toISOString(), | ||||
|     }) | ||||
|     fixture.detectChanges() | ||||
|     tick(3000) | ||||
|  | ||||
|     expect(copySpy).toHaveBeenCalled() | ||||
|     expect(refreshSpy).toHaveBeenCalled() | ||||
|   })) | ||||
|  | ||||
|   it('should show error on link creation if needed', () => { | ||||
|     component.documentId = 99 | ||||
|     component.expirationDays = 7 | ||||
|  | ||||
|     const expiration = new Date() | ||||
|     expiration.setDate(expiration.getDate() + 7) | ||||
|  | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|  | ||||
|     component.createLink() | ||||
|  | ||||
|     httpController | ||||
|       .expectOne(`${environment.apiBaseUrl}share_links/`) | ||||
|       .flush( | ||||
|         { error: 'Share link error' }, | ||||
|         { status: 500, statusText: 'error' } | ||||
|       ) | ||||
|     fixture.detectChanges() | ||||
|  | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support delete links & refresh', () => { | ||||
|     const deleteSpy = jest.spyOn(shareLinkService, 'delete') | ||||
|     deleteSpy.mockReturnValue(of(true)) | ||||
|     const refreshSpy = jest.spyOn(component, 'refresh') | ||||
|  | ||||
|     component.delete({ id: 12 } as PaperlessShareLink) | ||||
|     fixture.detectChanges() | ||||
|     expect(deleteSpy).toHaveBeenCalledWith({ id: 12 }) | ||||
|     expect(refreshSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should show error on delete if needed', () => { | ||||
|     const toastSpy = jest.spyOn(toastService, 'showError') | ||||
|     jest | ||||
|       .spyOn(shareLinkService, 'delete') | ||||
|       .mockReturnValueOnce(throwError(() => new Error('Unable to delete link'))) | ||||
|     component.delete(null) | ||||
|     fixture.detectChanges() | ||||
|     expect(toastSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should format days remaining', () => { | ||||
|     const now = new Date() | ||||
|     const expiration7days = new Date() | ||||
|     expiration7days.setDate(now.getDate() + 7) | ||||
|     const expiration1day = new Date() | ||||
|     expiration1day.setDate(now.getDate() + 1) | ||||
|  | ||||
|     expect( | ||||
|       component.getDaysRemaining({ | ||||
|         expiration: expiration7days.toISOString(), | ||||
|       } as PaperlessShareLink) | ||||
|     ).toEqual('7 days') | ||||
|     expect( | ||||
|       component.getDaysRemaining({ | ||||
|         expiration: expiration1day.toISOString(), | ||||
|       } as PaperlessShareLink) | ||||
|     ).toEqual('1 day') | ||||
|   }) | ||||
|  | ||||
|   // coverage | ||||
|   it('should support share', () => { | ||||
|     const link = { slug: '12345slug' } as PaperlessShareLink | ||||
|     if (!('share' in navigator)) | ||||
|       Object.defineProperty(navigator, 'share', { value: (obj: any) => {} }) | ||||
|     // const navigatorSpy = jest.spyOn(navigator, 'share') | ||||
|     component.share(link) | ||||
|     // expect(navigatorSpy).toHaveBeenCalledWith({ url: component.getShareUrl(link) }) | ||||
|   }) | ||||
| }) | ||||
| @@ -0,0 +1,149 @@ | ||||
| import { Component, Input, OnInit } from '@angular/core' | ||||
| import { first } from 'rxjs' | ||||
| import { | ||||
|   PaperlessShareLink, | ||||
|   PaperlessFileVersion, | ||||
| } from 'src/app/data/paperless-share-link' | ||||
| import { ShareLinkService } from 'src/app/services/rest/share-link.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { ClipboardService } from 'ngx-clipboard' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-share-links-dropdown', | ||||
|   templateUrl: './share-links-dropdown.component.html', | ||||
|   styleUrls: ['./share-links-dropdown.component.scss'], | ||||
| }) | ||||
| export class ShareLinksDropdownComponent implements OnInit { | ||||
|   EXPIRATION_OPTIONS = [ | ||||
|     { label: $localize`1 day`, value: 1 }, | ||||
|     { label: $localize`7 days`, value: 7 }, | ||||
|     { label: $localize`30 days`, value: 30 }, | ||||
|     { label: $localize`Never`, value: null }, | ||||
|   ] | ||||
|  | ||||
|   @Input() | ||||
|   title = $localize`Share Links` | ||||
|  | ||||
|   _documentId: number | ||||
|  | ||||
|   @Input() | ||||
|   set documentId(id: number) { | ||||
|     if (id !== undefined) { | ||||
|       this._documentId = id | ||||
|       this.refresh() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @Input() | ||||
|   disabled: boolean = false | ||||
|  | ||||
|   shareLinks: PaperlessShareLink[] | ||||
|  | ||||
|   loading: boolean = false | ||||
|  | ||||
|   copied: number | ||||
|  | ||||
|   expirationDays: number = 7 | ||||
|  | ||||
|   archiveVersion: boolean = true | ||||
|  | ||||
|   constructor( | ||||
|     private shareLinkService: ShareLinkService, | ||||
|     private toastService: ToastService, | ||||
|     private clipboardService: ClipboardService | ||||
|   ) {} | ||||
|  | ||||
|   ngOnInit(): void { | ||||
|     if (this._documentId !== undefined) this.refresh() | ||||
|   } | ||||
|  | ||||
|   refresh() { | ||||
|     if (this._documentId === undefined) return | ||||
|     this.loading = true | ||||
|     this.shareLinkService | ||||
|       .getLinksForDocument(this._documentId) | ||||
|       .pipe(first()) | ||||
|       .subscribe({ | ||||
|         next: (results) => { | ||||
|           this.loading = false | ||||
|           this.shareLinks = results | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`Error retrieving links`, | ||||
|             10000, | ||||
|             e | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   getShareUrl(link: PaperlessShareLink): string { | ||||
|     return `${environment.apiBaseUrl.replace('api', 'share')}${link.slug}` | ||||
|   } | ||||
|  | ||||
|   getDaysRemaining(link: PaperlessShareLink): string { | ||||
|     const days: number = Math.ceil( | ||||
|       (Date.parse(link.expiration) - Date.now()) / (1000 * 60 * 60 * 24) | ||||
|     ) | ||||
|     return days === 1 ? $localize`1 day` : $localize`${days} days` | ||||
|   } | ||||
|  | ||||
|   copy(link: PaperlessShareLink) { | ||||
|     this.clipboardService.copy(this.getShareUrl(link)) | ||||
|     this.copied = link.id | ||||
|     setTimeout(() => { | ||||
|       this.copied = null | ||||
|     }, 3000) | ||||
|   } | ||||
|  | ||||
|   canShare(link: PaperlessShareLink): boolean { | ||||
|     return ( | ||||
|       navigator?.canShare && navigator.canShare({ url: this.getShareUrl(link) }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   share(link: PaperlessShareLink) { | ||||
|     navigator.share({ url: this.getShareUrl(link) }) | ||||
|   } | ||||
|  | ||||
|   delete(link: PaperlessShareLink) { | ||||
|     this.shareLinkService.delete(link).subscribe({ | ||||
|       next: () => { | ||||
|         this.refresh() | ||||
|       }, | ||||
|       error: (e) => { | ||||
|         this.toastService.showError($localize`Error deleting link`, 10000, e) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   createLink() { | ||||
|     let expiration | ||||
|     if (this.expirationDays) { | ||||
|       expiration = new Date() | ||||
|       expiration.setDate(expiration.getDate() + this.expirationDays) | ||||
|     } | ||||
|     this.loading = true | ||||
|     this.shareLinkService | ||||
|       .createLinkForDocument( | ||||
|         this._documentId, | ||||
|         this.archiveVersion | ||||
|           ? PaperlessFileVersion.Archive | ||||
|           : PaperlessFileVersion.Original, | ||||
|         expiration | ||||
|       ) | ||||
|       .subscribe({ | ||||
|         next: (result) => { | ||||
|           this.loading = false | ||||
|           this.copy(result) | ||||
|           this.refresh() | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.loading = false | ||||
|           this.toastService.showError($localize`Error creating link`, 10000, e) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
| } | ||||
| @@ -10,6 +10,7 @@ import { ComponentFixture } from '@angular/core/testing' | ||||
| import { HttpClientTestingModule } from '@angular/common/http/testing' | ||||
| import { of } from 'rxjs' | ||||
| import { NgbModule } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { ClipboardService } from 'ngx-clipboard' | ||||
|  | ||||
| const toasts = [ | ||||
|   { | ||||
| @@ -41,6 +42,7 @@ describe('ToastsComponent', () => { | ||||
|   let component: ToastsComponent | ||||
|   let fixture: ComponentFixture<ToastsComponent> | ||||
|   let toastService: ToastService | ||||
|   let clipboardService: ClipboardService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     TestBed.configureTestingModule({ | ||||
| @@ -57,9 +59,10 @@ describe('ToastsComponent', () => { | ||||
|     }).compileComponents() | ||||
|  | ||||
|     fixture = TestBed.createComponent(ToastsComponent) | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     clipboardService = TestBed.inject(ClipboardService) | ||||
|  | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
|     fixture.detectChanges() | ||||
|   }) | ||||
| @@ -114,7 +117,7 @@ describe('ToastsComponent', () => { | ||||
|       'Error 2 message details' | ||||
|     ) | ||||
|  | ||||
|     const copySpy = jest.spyOn(navigator.clipboard, 'writeText') | ||||
|     const copySpy = jest.spyOn(clipboardService, 'copy') | ||||
|     component.copyError(toasts[2].error) | ||||
|     expect(copySpy).toHaveBeenCalled() | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
| import { ClipboardService } from 'ngx-clipboard' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-toasts', | ||||
| @@ -8,7 +9,10 @@ import { Toast, ToastService } from 'src/app/services/toast.service' | ||||
|   styleUrls: ['./toasts.component.scss'], | ||||
| }) | ||||
| export class ToastsComponent implements OnInit, OnDestroy { | ||||
|   constructor(private toastService: ToastService) {} | ||||
|   constructor( | ||||
|     private toastService: ToastService, | ||||
|     private clipboardService: ClipboardService | ||||
|   ) {} | ||||
|  | ||||
|   private subscription: Subscription | ||||
|  | ||||
| @@ -45,7 +49,7 @@ export class ToastsComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   public copyError(error: any) { | ||||
|     navigator.clipboard.writeText(JSON.stringify(error)) | ||||
|     this.clipboardService.copy(JSON.stringify(error)) | ||||
|     this.copied = true | ||||
|     setTimeout(() => { | ||||
|       this.copied = false | ||||
|   | ||||
| @@ -5,16 +5,15 @@ | ||||
|       <div class="input-group-text" i18n>of {{previewNumPages}}</div> | ||||
|     </div> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" [disabled]="!userIsOwner" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }"> | ||||
|     <button type="button" class="btn btn-sm btn-outline-danger me-4" (click)="delete()" [disabled]="!userIsOwner" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|             <use xlink:href="assets/bootstrap-icons.svg#trash" /> | ||||
|         </svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span> | ||||
|     </button> | ||||
|  | ||||
|     <div class="btn-group me-2"> | ||||
|  | ||||
|         <a [href]="downloadUrl" class="btn btn-sm btn-outline-primary"> | ||||
|             <svg class="buttonicon" fill="currentColor"> | ||||
|             <svg class="buttonicon me-md-1" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#download" /> | ||||
|             </svg><span class="d-none d-lg-inline ps-1" i18n>Download</span> | ||||
|         </a> | ||||
| @@ -25,20 +24,31 @@ | ||||
|                 <a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()" [disabled]="!userCanEdit"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|             <use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" /> | ||||
|         </svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span> | ||||
|     </button> | ||||
|     <div ngbDropdown> | ||||
|         <button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle> | ||||
|           <svg class="toolbaricon" fill="currentColor"> | ||||
|             <use xlink:href="assets/bootstrap-icons.svg#three-dots" /> | ||||
|           </svg> | ||||
|           <div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div> | ||||
|         </button> | ||||
|         <div ngbDropdownMenu aria-labelledby="actionsDropdown" class="shadow"> | ||||
|           <button ngbDropdownItem (click)="redoOcr()" [disabled]="!userCanEdit"> | ||||
|               <svg class="buttonicon-sm" fill="currentColor"> | ||||
|                   <use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" /> | ||||
|               </svg><span class="ps-1" i18n>Redo OCR</span> | ||||
|           </button> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="moreLike()"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|             <use xlink:href="assets/bootstrap-icons.svg#diagram-3" /> | ||||
|         </svg><span class="d-none d-lg-inline ps-1" i18n>More like this</span> | ||||
|     </button> | ||||
|           <button ngbDropdownItem (click)="moreLike()"> | ||||
|               <svg class="buttonicon-sm" fill="currentColor"> | ||||
|                   <use xlink:href="assets/bootstrap-icons.svg#diagram-3" /> | ||||
|               </svg><span class="ps-1" i18n>More like this</span> | ||||
|           </button> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <app-share-links-dropdown [documentId]="documentId" [disabled]="!userIsOwner" *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.ShareLink }"></app-share-links-dropdown> | ||||
|  | ||||
|     <button type="button" class="btn btn-sm btn-outline-primary me-2" i18n-title title="Close" (click)="close()"> | ||||
|         <svg class="buttonicon" fill="currentColor"> | ||||
|   | ||||
| @@ -66,6 +66,7 @@ import { TextComponent } from '../common/input/text/text.component' | ||||
| import { PageHeaderComponent } from '../common/page-header/page-header.component' | ||||
| import { DocumentNotesComponent } from '../document-notes/document-notes.component' | ||||
| import { DocumentDetailComponent } from './document-detail.component' | ||||
| import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component' | ||||
|  | ||||
| const doc: PaperlessDocument = { | ||||
|   id: 3, | ||||
| @@ -134,6 +135,7 @@ describe('DocumentDetailComponent', () => { | ||||
|         ConfirmDialogComponent, | ||||
|         PdfViewerComponent, | ||||
|         SafeUrlPipe, | ||||
|         ShareLinksDropdownComponent, | ||||
|       ], | ||||
|       providers: [ | ||||
|         DocumentTitlePipe, | ||||
|   | ||||
| @@ -63,6 +63,7 @@ import { EditDialogMode } from '../common/edit-dialog/edit-dialog.component' | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id' | ||||
| import { FilterRule } from 'src/app/data/filter-rule' | ||||
| import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter' | ||||
| import { ShareLinksDropdownComponent } from '../common/share-links-dropdown/share-links-dropdown.component' | ||||
|  | ||||
| enum DocumentDetailNavIDs { | ||||
|   Details = 1, | ||||
|   | ||||
							
								
								
									
										18
									
								
								src-ui/src/app/data/paperless-share-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src-ui/src/app/data/paperless-share-link.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import { ObjectWithPermissions } from './object-with-permissions' | ||||
|  | ||||
| export enum PaperlessFileVersion { | ||||
|   Archive = 'archive', | ||||
|   Original = 'original', | ||||
| } | ||||
|  | ||||
| export interface PaperlessShareLink extends ObjectWithPermissions { | ||||
|   created: string // Date | ||||
|  | ||||
|   expiration?: string // Date | ||||
|  | ||||
|   slug: string | ||||
|  | ||||
|   document: number // PaperlessDocument | ||||
|  | ||||
|   file_version: string | ||||
| } | ||||
| @@ -248,6 +248,10 @@ describe('PermissionsService', () => { | ||||
|         'view_log', | ||||
|         'view_comment', | ||||
|         'change_frontendsettings', | ||||
|         'add_sharelink', | ||||
|         'view_sharelink', | ||||
|         'change_sharelink', | ||||
|         'delete_sharelink', | ||||
|       ], | ||||
|       { | ||||
|         username: 'testuser', | ||||
|   | ||||
| @@ -24,6 +24,7 @@ export enum PermissionType { | ||||
|   User = '%s_user', | ||||
|   Group = '%s_group', | ||||
|   Admin = '%s_logentry', | ||||
|   ShareLink = '%s_sharelink', | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
|   | ||||
| @@ -10,7 +10,7 @@ export abstract class AbstractPaperlessService<T extends ObjectWithId> { | ||||
|  | ||||
|   constructor( | ||||
|     protected http: HttpClient, | ||||
|     private resourceName: string | ||||
|     protected resourceName: string | ||||
|   ) {} | ||||
|  | ||||
|   protected getResourceUrl(id: number = null, action: string = null): string { | ||||
|   | ||||
							
								
								
									
										42
									
								
								src-ui/src/app/services/rest/share-link.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src-ui/src/app/services/rest/share-link.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import { HttpTestingController } from '@angular/common/http/testing' | ||||
| import { TestBed } from '@angular/core/testing' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { environment } from 'src/environments/environment' | ||||
| import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec' | ||||
| import { ShareLinkService } from './share-link.service' | ||||
|  | ||||
| let httpTestingController: HttpTestingController | ||||
| let service: ShareLinkService | ||||
| let subscription: Subscription | ||||
| const endpoint = 'share_links' | ||||
|  | ||||
| // run common tests | ||||
| commonAbstractPaperlessServiceTests(endpoint, ShareLinkService) | ||||
|  | ||||
| describe(`Additional service tests for ShareLinkService`, () => { | ||||
|   beforeEach(() => { | ||||
|     // Dont need to setup again | ||||
|  | ||||
|     httpTestingController = TestBed.inject(HttpTestingController) | ||||
|     service = TestBed.inject(ShareLinkService) | ||||
|   }) | ||||
|  | ||||
|   afterEach(() => { | ||||
|     subscription?.unsubscribe() | ||||
|     httpTestingController.verify() | ||||
|   }) | ||||
|  | ||||
|   it('should support creating link for document', () => { | ||||
|     subscription = service.createLinkForDocument(0).subscribe() | ||||
|     httpTestingController | ||||
|       .expectOne(`${environment.apiBaseUrl}${endpoint}/`) | ||||
|       .flush({}) | ||||
|   }) | ||||
|  | ||||
|   it('should support get links for a document', () => { | ||||
|     subscription = service.getLinksForDocument(0).subscribe() | ||||
|     httpTestingController | ||||
|       .expectOne(`${environment.apiBaseUrl}documents/0/${endpoint}/`) | ||||
|       .flush({}) | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										36
									
								
								src-ui/src/app/services/rest/share-link.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src-ui/src/app/services/rest/share-link.service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import { Injectable } from '@angular/core' | ||||
| import { | ||||
|   PaperlessShareLink, | ||||
|   PaperlessFileVersion, | ||||
| } from 'src/app/data/paperless-share-link' | ||||
| import { AbstractNameFilterService } from './abstract-name-filter-service' | ||||
| import { HttpClient } from '@angular/common/http' | ||||
| import { Observable } from 'rxjs' | ||||
|  | ||||
| @Injectable({ | ||||
|   providedIn: 'root', | ||||
| }) | ||||
| export class ShareLinkService extends AbstractNameFilterService<PaperlessShareLink> { | ||||
|   constructor(http: HttpClient) { | ||||
|     super(http, 'share_links') | ||||
|   } | ||||
|  | ||||
|   getLinksForDocument(documentId: number): Observable<PaperlessShareLink[]> { | ||||
|     return this.http.get<PaperlessShareLink[]>( | ||||
|       `${this.baseUrl}documents/${documentId}/${this.resourceName}/` | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   createLinkForDocument( | ||||
|     documentId: number, | ||||
|     file_version: PaperlessFileVersion = PaperlessFileVersion.Archive, | ||||
|     expiration: Date = null | ||||
|   ) { | ||||
|     this.clearCache() | ||||
|     return this.http.post<PaperlessShareLink>(this.getResourceUrl(), { | ||||
|       document: documentId, | ||||
|       file_version, | ||||
|       expiration: expiration?.toISOString(), | ||||
|     }) | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon