mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Enhancement: filter by file type (#8946)
This commit is contained in:
		| @@ -1167,7 +1167,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">170</context> | ||||
|           <context context-type="linenumber">173</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7314814725704332646" datatype="html"> | ||||
| @@ -2162,7 +2162,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">225</context> | ||||
|           <context context-type="linenumber">224</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/saved-views/saved-views.component.html</context> | ||||
| @@ -2196,11 +2196,11 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">221</context> | ||||
|           <context context-type="linenumber">220</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">338</context> | ||||
|           <context context-type="linenumber">337</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1373208150912772963" datatype="html"> | ||||
| @@ -2242,7 +2242,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">340</context> | ||||
|           <context context-type="linenumber">339</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.ts</context> | ||||
| @@ -2577,7 +2577,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">342</context> | ||||
|           <context context-type="linenumber">341</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/workflows/workflows.component.ts</context> | ||||
| @@ -2731,7 +2731,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||
|           <context context-type="linenumber">106</context> | ||||
|           <context context-type="linenumber">107</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7886570921510760899" datatype="html"> | ||||
| @@ -2750,7 +2750,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||
|           <context context-type="linenumber">93</context> | ||||
|           <context context-type="linenumber">94</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context> | ||||
| @@ -2781,7 +2781,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||
|           <context context-type="linenumber">119</context> | ||||
|           <context context-type="linenumber">120</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5421255270838137624" datatype="html"> | ||||
| @@ -2796,7 +2796,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> | ||||
|           <context context-type="linenumber">132</context> | ||||
|           <context context-type="linenumber">133</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3188389494264426470" datatype="html"> | ||||
| @@ -6192,7 +6192,7 @@ | ||||
|         <source>Other</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts</context> | ||||
|           <context context-type="linenumber">79</context> | ||||
|           <context context-type="linenumber">83</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8187573012244728580" datatype="html"> | ||||
| @@ -6401,7 +6401,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">158</context> | ||||
|           <context context-type="linenumber">160</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/data/document.ts</context> | ||||
| @@ -7029,7 +7029,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">166</context> | ||||
|           <context context-type="linenumber">168</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6475890479659129881" datatype="html"> | ||||
| @@ -7631,7 +7631,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">163</context> | ||||
|           <context context-type="linenumber">165</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/data/document.ts</context> | ||||
| @@ -7820,147 +7820,154 @@ | ||||
|         <source>Title & content</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">161</context> | ||||
|           <context context-type="linenumber">163</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7408932238599462499" datatype="html"> | ||||
|         <source>File type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">170</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="2649431021108393503" datatype="html"> | ||||
|         <source>More like</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">176</context> | ||||
|           <context context-type="linenumber">179</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3697582909018473071" datatype="html"> | ||||
|         <source>equals</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">182</context> | ||||
|           <context context-type="linenumber">185</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5325481293405718739" datatype="html"> | ||||
|         <source>is empty</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">186</context> | ||||
|           <context context-type="linenumber">189</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6166785695326182482" datatype="html"> | ||||
|         <source>is not empty</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">190</context> | ||||
|           <context context-type="linenumber">193</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4686622206659266699" datatype="html"> | ||||
|         <source>greater than</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">194</context> | ||||
|           <context context-type="linenumber">197</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8014012170270529279" datatype="html"> | ||||
|         <source>less than</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|           <context context-type="linenumber">201</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5195932016807797291" datatype="html"> | ||||
|         <source>Correspondent: <x id="PH" equiv-text="this.correspondents.find((c) => c.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">230,232</context> | ||||
|           <context context-type="linenumber">233,235</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8170755470576301659" datatype="html"> | ||||
|         <source>Without correspondent</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">234</context> | ||||
|           <context context-type="linenumber">237</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="317796810569008208" datatype="html"> | ||||
|         <source>Document type: <x id="PH" equiv-text="this.documentTypes.find((dt) => dt.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">240,242</context> | ||||
|           <context context-type="linenumber">243,245</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4362173610367509215" datatype="html"> | ||||
|         <source>Without document type</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">244</context> | ||||
|           <context context-type="linenumber">247</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="232202047340644471" datatype="html"> | ||||
|         <source>Storage path: <x id="PH" equiv-text="this.storagePaths.find((sp) => sp.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">250,252</context> | ||||
|           <context context-type="linenumber">253,255</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1562820715074533164" datatype="html"> | ||||
|         <source>Without storage path</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">254</context> | ||||
|           <context context-type="linenumber">257</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8180755793012580465" datatype="html"> | ||||
|         <source>Tag: <x id="PH" equiv-text="this.tags.find((t) => t.id == +rule.value)?.name"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">258,260</context> | ||||
|           <context context-type="linenumber">261,263</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6494566478302448576" datatype="html"> | ||||
|         <source>Without any tag</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">264</context> | ||||
|           <context context-type="linenumber">267</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8644099678903817943" datatype="html"> | ||||
|         <source>Custom fields query</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">268</context> | ||||
|           <context context-type="linenumber">271</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6523384805359286307" datatype="html"> | ||||
|         <source>Title: <x id="PH" equiv-text="rule.value"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">271</context> | ||||
|           <context context-type="linenumber">274</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1872523635812236432" datatype="html"> | ||||
|         <source>ASN: <x id="PH" equiv-text="rule.value"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">274</context> | ||||
|           <context context-type="linenumber">277</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="102674688969746976" datatype="html"> | ||||
|         <source>Owner: <x id="PH" equiv-text="rule.value"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">277</context> | ||||
|           <context context-type="linenumber">280</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3550877650686009106" datatype="html"> | ||||
|         <source>Owner not in: <x id="PH" equiv-text="rule.value"/></source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">280</context> | ||||
|           <context context-type="linenumber">283</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1082034558646673343" datatype="html"> | ||||
|         <source>Without an owner</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context> | ||||
|           <context context-type="linenumber">283</context> | ||||
|           <context context-type="linenumber">286</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="7210076240260527720" datatype="html"> | ||||
| @@ -8455,7 +8462,7 @@ | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">325</context> | ||||
|           <context context-type="linenumber">324</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4010735610815226758" datatype="html"> | ||||
| @@ -8538,7 +8545,7 @@ | ||||
|         <source>Automatic</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">117</context> | ||||
|           <context context-type="linenumber">116</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/data/matching-model.ts</context> | ||||
| @@ -8549,7 +8556,7 @@ | ||||
|         <source>None</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">119</context> | ||||
|           <context context-type="linenumber">118</context> | ||||
|         </context-group> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/data/matching-model.ts</context> | ||||
| @@ -8560,70 +8567,70 @@ | ||||
|         <source>Successfully created <x id="PH" equiv-text="this.typeName"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">178</context> | ||||
|           <context context-type="linenumber">177</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="3928835053823658072" datatype="html"> | ||||
|         <source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">183</context> | ||||
|           <context context-type="linenumber">182</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4835942264662718903" datatype="html"> | ||||
|         <source>Successfully updated <x id="PH" equiv-text="this.typeName"/> "<x id="PH_1" equiv-text="object.name"/>".</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">198</context> | ||||
|           <context context-type="linenumber">197</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6442673774206210733" datatype="html"> | ||||
|         <source>Error occurred while saving <x id="PH" equiv-text="this.typeName"/>.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">203</context> | ||||
|           <context context-type="linenumber">202</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8371896857609524947" datatype="html"> | ||||
|         <source>Associated documents will not be deleted.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">223</context> | ||||
|           <context context-type="linenumber">222</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="6639207128255974941" datatype="html"> | ||||
|         <source>Error while deleting element</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">239</context> | ||||
|           <context context-type="linenumber">238</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="4863024195229581844" datatype="html"> | ||||
|         <source>Permissions updated successfully</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">318</context> | ||||
|           <context context-type="linenumber">317</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1464476612812630086" datatype="html"> | ||||
|         <source>This operation will permanently delete all objects.</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">339</context> | ||||
|           <context context-type="linenumber">338</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="5897787932098828336" datatype="html"> | ||||
|         <source>Objects deleted successfully</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">353</context> | ||||
|           <context context-type="linenumber">352</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="8273353839648035634" datatype="html"> | ||||
|         <source>Error deleting objects</source> | ||||
|         <context-group purpose="location"> | ||||
|           <context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context> | ||||
|           <context context-type="linenumber">359</context> | ||||
|           <context context-type="linenumber">358</context> | ||||
|         </context-group> | ||||
|       </trans-unit> | ||||
|       <trans-unit id="1930477323485553035" datatype="html"> | ||||
|   | ||||
| @@ -56,6 +56,7 @@ | ||||
|                   [ngbPopover]="getFileTypeName(filetype)" | ||||
|                   i18n-ngbPopover | ||||
|                   triggers="mouseenter:mouseleave" | ||||
|                   (click)="filterByFileType(filetype)" | ||||
|                   [attr.aria-label]="getFileTypeName(filetype)" | ||||
|                   [class.me-1px]="!last" | ||||
|                   [style.width]="getFileTypePercent(filetype) + '%'" | ||||
| @@ -70,7 +71,7 @@ | ||||
|           <div class="d-flex flex-wrap align-items-start"> | ||||
|             @for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index) { | ||||
|               <div class="d-flex"> | ||||
|                 <div class="text-nowrap me-2"> | ||||
|                 <div class="text-nowrap me-2" [class.cursor-pointer]="!filetype.is_other" (click)="filterByFileType(filetype)"> | ||||
|                   <span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span> | ||||
|                   <small class="text-nowrap"><span class="fw-bold">{{ getFileTypeExtension(filetype) }}</span> <span class="text-muted">({{getFileTypePercent(filetype) | number: '1.0-1'}}%)</span></small> | ||||
|                 </div> | ||||
|   | ||||
| @@ -9,8 +9,10 @@ import { RouterTestingModule } from '@angular/router/testing' | ||||
| import { NgbModule } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Subject } from 'rxjs' | ||||
| import { routes } from 'src/app/app-routing.module' | ||||
| import { FILTER_MIME_TYPE } from 'src/app/data/filter-rule-type' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { PermissionsGuard } from 'src/app/guards/permissions.guard' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { | ||||
|   FileStatus, | ||||
|   WebsocketStatusService, | ||||
| @@ -24,6 +26,7 @@ describe('StatisticsWidgetComponent', () => { | ||||
|   let fixture: ComponentFixture<StatisticsWidgetComponent> | ||||
|   let httpTestingController: HttpTestingController | ||||
|   let websocketStatusService: WebsocketStatusService | ||||
|   let documentListViewService: DocumentListViewService | ||||
|   const fileStatusSubject = new Subject<FileStatus>() | ||||
|  | ||||
|   beforeEach(async () => { | ||||
| @@ -48,6 +51,7 @@ describe('StatisticsWidgetComponent', () => { | ||||
|     jest | ||||
|       .spyOn(websocketStatusService, 'onDocumentConsumptionFinished') | ||||
|       .mockReturnValue(fileStatusSubject) | ||||
|     documentListViewService = TestBed.inject(DocumentListViewService) | ||||
|     component = fixture.componentInstance | ||||
|  | ||||
|     httpTestingController = TestBed.inject(HttpTestingController) | ||||
| @@ -231,4 +235,26 @@ describe('StatisticsWidgetComponent', () => { | ||||
|       'CurrentASN:' | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   it('should support quick filter by mime type', () => { | ||||
|     const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') | ||||
|     component.filterByFileType({ | ||||
|       mime_type: 'application/pdf', | ||||
|       mime_type_count: 160, | ||||
|     }) | ||||
|     expect(qfSpy).toHaveBeenCalledWith([ | ||||
|       { | ||||
|         rule_type: FILTER_MIME_TYPE, | ||||
|         value: 'application/pdf', | ||||
|       }, | ||||
|     ]) | ||||
|  | ||||
|     qfSpy.mockClear() | ||||
|     component.filterByFileType({ | ||||
|       mime_type: 'Other', | ||||
|       mime_type_count: 160, | ||||
|       is_other: true, | ||||
|     }) | ||||
|     expect(qfSpy).not.toHaveBeenCalled() | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -6,7 +6,10 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap' | ||||
| import * as mimeTypeNames from 'mime-names' | ||||
| import { first, Subject, Subscription, takeUntil } from 'rxjs' | ||||
| import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component' | ||||
| import { FILTER_HAS_TAGS_ANY } from 'src/app/data/filter-rule-type' | ||||
| import { | ||||
|   FILTER_HAS_TAGS_ANY, | ||||
|   FILTER_MIME_TYPE, | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { WebsocketStatusService } from 'src/app/services/websocket-status.service' | ||||
| @@ -29,6 +32,7 @@ export interface Statistics { | ||||
| interface DocumentFileType { | ||||
|   mime_type: string | ||||
|   mime_type_count: number | ||||
|   is_other?: boolean | ||||
| } | ||||
|  | ||||
| @Component({ | ||||
| @@ -77,6 +81,7 @@ export class StatisticsWidgetComponent | ||||
|             statistics.document_file_type_counts.slice(0, fileTypeMax) | ||||
|           statistics.document_file_type_counts.push({ | ||||
|             mime_type: $localize`Other`, | ||||
|             is_other: true, | ||||
|             mime_type_count: others.reduce( | ||||
|               (currentValue, documentFileType) => | ||||
|                 documentFileType.mime_type_count + currentValue, | ||||
| @@ -132,4 +137,14 @@ export class StatisticsWidgetComponent | ||||
|       }, | ||||
|     ]) | ||||
|   } | ||||
|  | ||||
|   filterByFileType(filetype: DocumentFileType) { | ||||
|     if (filetype.is_other) return | ||||
|     this.documentListViewService.quickFilter([ | ||||
|       { | ||||
|         rule_type: FILTER_MIME_TYPE, | ||||
|         value: filetype.mime_type, | ||||
|       }, | ||||
|     ]) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -60,6 +60,7 @@ import { | ||||
|   FILTER_HAS_STORAGE_PATH_ANY, | ||||
|   FILTER_HAS_TAGS_ALL, | ||||
|   FILTER_HAS_TAGS_ANY, | ||||
|   FILTER_MIME_TYPE, | ||||
|   FILTER_OWNER, | ||||
|   FILTER_OWNER_ANY, | ||||
|   FILTER_OWNER_DOES_NOT_INCLUDE, | ||||
| @@ -389,6 +390,18 @@ describe('FilterEditorComponent', () => { | ||||
|     expect(component.textFilterModifier).toEqual('less') // TEXT_FILTER_MODIFIER_LT | ||||
|   })) | ||||
|  | ||||
|   it('should ingest text filter rules for mime type', fakeAsync(() => { | ||||
|     expect(component.textFilter).toEqual(null) | ||||
|     component.filterRules = [ | ||||
|       { | ||||
|         rule_type: FILTER_MIME_TYPE, | ||||
|         value: 'pdf', | ||||
|       }, | ||||
|     ] | ||||
|     expect(component.textFilter).toEqual('pdf') | ||||
|     expect(component.textFilterTarget).toEqual('mime-type') // TEXT_FILTER_TARGET_MIME_TYPE | ||||
|   })) | ||||
|  | ||||
|   it('should ingest text filter rules for fulltext query', fakeAsync(() => { | ||||
|     expect(component.textFilter).toEqual(null) | ||||
|     component.filterRules = [ | ||||
| @@ -1222,12 +1235,30 @@ describe('FilterEditorComponent', () => { | ||||
|     ]) | ||||
|   })) | ||||
|  | ||||
|   it('should convert user input to correct filter rules on mime type', fakeAsync(() => { | ||||
|     component.textFilterInput.nativeElement.value = 'pdf' | ||||
|     component.textFilterInput.nativeElement.dispatchEvent(new Event('input')) | ||||
|     const textFieldTargetDropdown = fixture.debugElement.queryAll( | ||||
|       By.directive(NgbDropdownItem) | ||||
|     )[4] | ||||
|     textFieldTargetDropdown.triggerEventHandler('click') // TEXT_FILTER_TARGET_MIME_TYPE | ||||
|     fixture.detectChanges() | ||||
|     tick(400) | ||||
|     expect(component.textFilterTarget).toEqual('mime-type') | ||||
|     expect(component.filterRules).toEqual([ | ||||
|       { | ||||
|         rule_type: FILTER_MIME_TYPE, | ||||
|         value: 'pdf', | ||||
|       }, | ||||
|     ]) | ||||
|   })) | ||||
|  | ||||
|   it('should convert user input to correct filter rules on full text query', fakeAsync(() => { | ||||
|     component.textFilterInput.nativeElement.value = 'foo' | ||||
|     component.textFilterInput.nativeElement.dispatchEvent(new Event('input')) | ||||
|     const textFieldTargetDropdown = fixture.debugElement.queryAll( | ||||
|       By.directive(NgbDropdownItem) | ||||
|     )[4] | ||||
|     )[5] | ||||
|     textFieldTargetDropdown.triggerEventHandler('click') // TEXT_FILTER_TARGET_ASN | ||||
|     fixture.detectChanges() | ||||
|     tick(400) | ||||
| @@ -1594,7 +1625,7 @@ describe('FilterEditorComponent', () => { | ||||
|     component.textFilterInput.nativeElement.dispatchEvent(new Event('input')) | ||||
|     const textFieldTargetDropdown = fixture.debugElement.queryAll( | ||||
|       By.directive(NgbDropdownItem) | ||||
|     )[4] | ||||
|     )[5] | ||||
|     textFieldTargetDropdown.triggerEventHandler('click') | ||||
|     fixture.detectChanges() | ||||
|     tick(400) | ||||
|   | ||||
| @@ -66,6 +66,7 @@ import { | ||||
|   FILTER_HAS_STORAGE_PATH_ANY, | ||||
|   FILTER_HAS_TAGS_ALL, | ||||
|   FILTER_HAS_TAGS_ANY, | ||||
|   FILTER_MIME_TYPE, | ||||
|   FILTER_OWNER, | ||||
|   FILTER_OWNER_ANY, | ||||
|   FILTER_OWNER_DOES_NOT_INCLUDE, | ||||
| @@ -126,6 +127,7 @@ const TEXT_FILTER_TARGET_ASN = 'asn' | ||||
| const TEXT_FILTER_TARGET_FULLTEXT_QUERY = 'fulltext-query' | ||||
| const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = 'fulltext-morelike' | ||||
| const TEXT_FILTER_TARGET_CUSTOM_FIELDS = 'custom-fields' | ||||
| const TEXT_FILTER_TARGET_MIME_TYPE = 'mime-type' | ||||
|  | ||||
| const TEXT_FILTER_MODIFIER_EQUALS = 'equals' | ||||
| const TEXT_FILTER_MODIFIER_NULL = 'is null' | ||||
| @@ -165,6 +167,7 @@ const DEFAULT_TEXT_FILTER_TARGET_OPTIONS = [ | ||||
|     id: TEXT_FILTER_TARGET_CUSTOM_FIELDS, | ||||
|     name: $localize`Custom fields`, | ||||
|   }, | ||||
|   { id: TEXT_FILTER_TARGET_MIME_TYPE, name: $localize`File type` }, | ||||
|   { | ||||
|     id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, | ||||
|     name: $localize`Advanced search`, | ||||
| @@ -416,6 +419,10 @@ export class FilterEditorComponent | ||||
|           this._textFilter = rule.value | ||||
|           this.textFilterTarget = TEXT_FILTER_TARGET_CUSTOM_FIELDS | ||||
|           break | ||||
|         case FILTER_MIME_TYPE: | ||||
|           this.textFilterTarget = TEXT_FILTER_TARGET_MIME_TYPE | ||||
|           this._textFilter = rule.value | ||||
|           break | ||||
|         case FILTER_FULLTEXT_QUERY: | ||||
|           let allQueryArgs = rule.value.split(',') | ||||
|           let textQueryArgs = [] | ||||
| @@ -729,6 +736,15 @@ export class FilterEditorComponent | ||||
|         value: this._textFilter, | ||||
|       }) | ||||
|     } | ||||
|     if ( | ||||
|       this._textFilter && | ||||
|       this.textFilterTarget == TEXT_FILTER_TARGET_MIME_TYPE | ||||
|     ) { | ||||
|       filterRules.push({ | ||||
|         rule_type: FILTER_MIME_TYPE, | ||||
|         value: this._textFilter, | ||||
|       }) | ||||
|     } | ||||
|     if ( | ||||
|       this._textFilter && | ||||
|       this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY | ||||
|   | ||||
| @@ -62,6 +62,8 @@ export const FILTER_HAS_ANY_CUSTOM_FIELDS = 41 | ||||
|  | ||||
| export const FILTER_CUSTOM_FIELDS_QUERY = 42 | ||||
|  | ||||
| export const FILTER_MIME_TYPE = 47 | ||||
|  | ||||
| export const FILTER_RULE_TYPES: FilterRuleType[] = [ | ||||
|   { | ||||
|     id: FILTER_TITLE, | ||||
| @@ -354,6 +356,12 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [ | ||||
|     datatype: 'string', | ||||
|     multi: false, | ||||
|   }, | ||||
|   { | ||||
|     id: FILTER_MIME_TYPE, | ||||
|     filtervar: 'mime_type', | ||||
|     datatype: 'string', | ||||
|     multi: false, | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| export interface FilterRuleType { | ||||
|   | ||||
| @@ -215,6 +215,14 @@ class CustomFieldsFilter(Filter): | ||||
|             return qs | ||||
|  | ||||
|  | ||||
| class MimeTypeFilter(Filter): | ||||
|     def filter(self, qs, value): | ||||
|         if value: | ||||
|             return qs.filter(mime_type__icontains=value) | ||||
|         else: | ||||
|             return qs | ||||
|  | ||||
|  | ||||
| class SelectField(serializers.CharField): | ||||
|     def __init__(self, custom_field: CustomField): | ||||
|         self._options = custom_field.extra_data["select_options"] | ||||
| @@ -710,6 +718,8 @@ class DocumentFilterSet(FilterSet): | ||||
|  | ||||
|     shared_by__id = SharedByUser() | ||||
|  | ||||
|     mime_type = MimeTypeFilter() | ||||
|  | ||||
|     class Meta: | ||||
|         model = Document | ||||
|         fields = { | ||||
|   | ||||
| @@ -62,6 +62,7 @@ class Migration(migrations.Migration): | ||||
|                     (44, "created from"), | ||||
|                     (45, "added to"), | ||||
|                     (46, "added from"), | ||||
|                     (47, "mime type is"), | ||||
|                 ], | ||||
|                 verbose_name="rule type", | ||||
|             ), | ||||
|   | ||||
| @@ -526,6 +526,7 @@ class SavedViewFilterRule(models.Model): | ||||
|         (44, _("created from")), | ||||
|         (45, _("added to")), | ||||
|         (46, _("added from")), | ||||
|         (47, _("mime type is")), | ||||
|     ] | ||||
|  | ||||
|     saved_view = models.ForeignKey( | ||||
|   | ||||
| @@ -639,6 +639,13 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): | ||||
|         self.assertEqual(len(results), 1) | ||||
|         self.assertEqual(results[0]["id"], doc3.id) | ||||
|  | ||||
|         response = self.client.get( | ||||
|             "/api/documents/?mime_type=pdf", | ||||
|         ) | ||||
|         self.assertEqual(response.status_code, status.HTTP_200_OK) | ||||
|         results = response.data["results"] | ||||
|         self.assertEqual(len(results), 3) | ||||
|  | ||||
|     def test_custom_field_select_filter(self): | ||||
|         """ | ||||
|         GIVEN: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon