@brftech/filex-core 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,20 @@ const props = defineProps<{
19
19
  /** Server ceiling on a new link's life in days (capabilities.share_max_ttl_days).
20
20
  * undefined/0 = no ceiling. The expiry choices are derived from it. */
21
21
  shareMaxTtlDays?: number;
22
+ /**
23
+ * surucu:d1 — which tab this modal opens on. Absent = 'perms', the behaviour
24
+ * every existing caller has.
25
+ *
26
+ * The drive shell's "Request files" needs 'drop': the file-drop link already
27
+ * lives in this modal, and a menu entry that opens it on the permissions tab
28
+ * and leaves the user to find the third tab is a menu entry that lied about
29
+ * what it does.
30
+ *
31
+ * ⚠ A hint, not a lock: `load()` still falls back to 'share' when the caller
32
+ * turns out not to be an owner, because the permissions tab is not theirs to
33
+ * see.
34
+ */
35
+ initialTab?: 'perms' | 'share' | 'drop';
22
36
  }>();
23
37
  const emit = defineEmits<{ (e: 'close'): void }>();
24
38
 
@@ -37,7 +51,7 @@ const pathParts = computed(() => {
37
51
  });
38
52
 
39
53
  type Tab = 'perms' | 'share' | 'drop';
40
- const tab = ref<Tab>('perms');
54
+ const tab = ref<Tab>(props.initialTab ?? 'perms');
41
55
  const canManage = ref(false); // owner/admin → can see the permissions tab
42
56
 
43
57
  // ── permissions state ──
@@ -185,7 +199,9 @@ async function reload() {
185
199
  if (st === 403) {
186
200
  canManage.value = false;
187
201
  // Editor (not owner): no permissions tab → fall back to the link tab.
188
- tab.value = 'share';
202
+ // Unless the caller asked for a specific one: "Request files" opens
203
+ // 'drop', which an editor may perfectly well use.
204
+ if (!props.initialTab || props.initialTab === 'perms') tab.value = 'share';
189
205
  } else {
190
206
  err.value = e instanceof Error ? e.message : String(e);
191
207
  }
@@ -3914,6 +3914,20 @@ filex-explorer {
3914
3914
  font-size: 12.5px;
3915
3915
  color: var(--fe-text-muted);
3916
3916
  }
3917
+ /* Why the escrow key does not apply to THIS folder. Deliberately not muted
3918
+ to the point of invisibility: it is the answer to a question the reader
3919
+ is already asking, and the pre-adoption case has no other surface. */
3920
+ .fe-e2e-recover__note {
3921
+ margin: 0;
3922
+ padding: 8px 10px;
3923
+ font-size: 12.5px;
3924
+ line-height: 1.45;
3925
+ color: var(--fe-text);
3926
+ border: 1px solid var(--fe-border);
3927
+ border-left: 3px solid var(--fe-warning, #f59e0b);
3928
+ border-radius: var(--fe-radius);
3929
+ background: var(--fe-bg-elev);
3930
+ }
3917
3931
  .fe-e2e-recover__tabs {
3918
3932
  display: flex;
3919
3933
  gap: 4px;
@@ -3988,6 +4002,36 @@ filex-explorer {
3988
4002
  gap: 8px;
3989
4003
  flex: none;
3990
4004
  }
4005
+ /* wiring:e2 escrow-offer — the same strip, asking a heavier question:
4006
+ accepting hands the operator a permanent second key. Amber rather than
4007
+ the recovery offer's primary tint, so it does not read as routine. */
4008
+ .fe-e2e-upgrade--escrow {
4009
+ border-left-color: var(--fe-warning, #f59e0b);
4010
+ background: color-mix(in srgb, var(--fe-warning, #f59e0b) 7%, transparent);
4011
+ }
4012
+ /* The link to the honest long version has to LOOK like a link: it inherits
4013
+ the warning colour, and without this it read as ordinary sentence text —
4014
+ somebody being asked to hand over a key should be able to see the way to
4015
+ the page that explains what they are handing over. */
4016
+ .fe-e2e-upgrade__escrow a {
4017
+ color: inherit;
4018
+ text-decoration: underline;
4019
+ text-underline-offset: 2px;
4020
+ font-weight: 600;
4021
+ }
4022
+ .fe-e2e-upgrade__escrow a:hover,
4023
+ .fe-e2e-upgrade__escrow a:focus-visible {
4024
+ text-decoration-thickness: 2px;
4025
+ }
4026
+ .fe-e2e-upgrade__pw {
4027
+ display: flex;
4028
+ flex-direction: column;
4029
+ gap: 4px;
4030
+ margin-top: 8px;
4031
+ max-width: 320px;
4032
+ font-size: 12.5px;
4033
+ color: var(--fe-text-muted);
4034
+ }
3991
4035
  /* === /wiring:e2 === */
3992
4036
 
3993
4037
  /* === ui-fix — toolbar tek satır + katlama + ölçüm şeridi ============= */
@@ -4622,3 +4666,450 @@ filex-explorer {
4622
4666
  margin-left: auto;
4623
4667
  }
4624
4668
  /* --- /gezinti:g1 --- */
4669
+
4670
+ /* === surucu:d1 — the Drive shell (uiProfile: 'drive', GitHub #14) ======
4671
+ Everything here is additive and gated on a class the other two profiles
4672
+ never carry, so `standard` and `simple` render exactly what they rendered
4673
+ before this block existed.
4674
+
4675
+ ⚠ Unscoped and prefixed, like the rest of this file. A `<style scoped>` in
4676
+ an SFC compiles to `.cls[data-v-HASH]`, and the hash does not match in the
4677
+ web-component build — the rules silently stop applying in every embed, which
4678
+ is how the share dialog once shipped as raw unstyled HTML.
4679
+ ---------------------------------------------------------------------- */
4680
+
4681
+ /* ── the header ───────────────────────────────────────────────────────── */
4682
+ .fe-toolbar--drive {
4683
+ gap: 10px;
4684
+ padding: 8px 12px;
4685
+ }
4686
+ /* The field takes the row. `min-width: 0` because a flex item's default `auto`
4687
+ minimum is its content width and an <input> reports a wide one — without it
4688
+ the header refuses to shrink and the whole explorer grows a horizontal
4689
+ scrollbar at 390px. */
4690
+ .fe-toolbar__search-group--drive {
4691
+ flex: 1 1 auto;
4692
+ min-width: 0;
4693
+ gap: 8px;
4694
+ }
4695
+ .fe-drivesearch {
4696
+ position: relative;
4697
+ display: flex;
4698
+ align-items: center;
4699
+ gap: 8px;
4700
+ flex: 1 1 auto;
4701
+ min-width: 0;
4702
+ max-width: 720px;
4703
+ padding: 0 10px 0 12px;
4704
+ height: 40px;
4705
+ border-radius: 999px;
4706
+ border: 1px solid transparent;
4707
+ background: var(--fe-bg-elev);
4708
+ color: var(--fe-text);
4709
+ }
4710
+ .fe-drivesearch:focus-within {
4711
+ background: var(--fe-bg);
4712
+ border-color: var(--fe-border-strong);
4713
+ box-shadow: var(--fe-shadow-sm);
4714
+ }
4715
+ .fe-drivesearch__glass {
4716
+ flex: 0 0 auto;
4717
+ width: 18px;
4718
+ height: 18px;
4719
+ color: var(--fe-text-muted);
4720
+ }
4721
+ .fe-drivesearch__input {
4722
+ flex: 1 1 auto;
4723
+ min-width: 0;
4724
+ border: 0;
4725
+ background: transparent;
4726
+ color: inherit;
4727
+ font: inherit;
4728
+ font-size: 14px;
4729
+ outline: none;
4730
+ }
4731
+ .fe-drivesearch__input::placeholder {
4732
+ color: var(--fe-text-muted);
4733
+ }
4734
+ /* The ⌘K chip. A real button, not decoration: it is the one visible door to
4735
+ the palette in this shell, so it has to be clickable and tab-reachable —
4736
+ printing a shortcut nobody can press with a mouse is a hint, not a control. */
4737
+ .fe-drivesearch__kbd {
4738
+ flex: 0 0 auto;
4739
+ padding: 3px 8px;
4740
+ border: 1px solid var(--fe-border);
4741
+ border-radius: var(--fe-radius-sm);
4742
+ background: var(--fe-bg);
4743
+ color: var(--fe-text-muted);
4744
+ font: inherit;
4745
+ font-size: 11.5px;
4746
+ letter-spacing: 0.02em;
4747
+ cursor: pointer;
4748
+ white-space: nowrap;
4749
+ }
4750
+ .fe-drivesearch__kbd:hover {
4751
+ color: var(--fe-text);
4752
+ border-color: var(--fe-border-strong);
4753
+ }
4754
+ .fe-drivesearch__kbd:focus-visible {
4755
+ outline: 2px solid var(--fe-primary);
4756
+ outline-offset: 1px;
4757
+ }
4758
+
4759
+ /* ── the "+ New" button ───────────────────────────────────────────────── */
4760
+ .fe-sidenav__new {
4761
+ display: inline-flex;
4762
+ align-items: center;
4763
+ gap: 10px;
4764
+ width: 100%;
4765
+ padding: 11px 18px;
4766
+ border-radius: 999px;
4767
+ border: 1px solid var(--fe-primary);
4768
+ background: var(--fe-primary);
4769
+ color: var(--fe-text-on-primary);
4770
+ font: inherit;
4771
+ font-size: 14px;
4772
+ font-weight: 600;
4773
+ text-align: left;
4774
+ white-space: nowrap;
4775
+ overflow: hidden;
4776
+ cursor: pointer;
4777
+ box-shadow: var(--fe-shadow-sm);
4778
+ }
4779
+ .fe-sidenav__new:hover {
4780
+ background: var(--fe-primary-hover);
4781
+ border-color: var(--fe-primary-hover);
4782
+ }
4783
+ .fe-sidenav__new:focus-visible {
4784
+ outline: 2px solid var(--fe-primary);
4785
+ outline-offset: 2px;
4786
+ }
4787
+ .fe-sidenav__new .fe-ficon {
4788
+ width: 20px;
4789
+ height: 20px;
4790
+ flex: 0 0 auto;
4791
+ }
4792
+ /* On the rail it becomes a circle: a pill with no label is a stretched blob. */
4793
+ .fe-sidenav--rail .fe-sidenav__new {
4794
+ width: 40px;
4795
+ height: 40px;
4796
+ padding: 0;
4797
+ justify-content: center;
4798
+ border-radius: 50%;
4799
+ }
4800
+
4801
+ /* ── the storage line ─────────────────────────────────────────────────── */
4802
+ .fe-sidenav__quota {
4803
+ flex: 0 0 auto;
4804
+ padding: 10px 12px 12px;
4805
+ border-top: 1px solid var(--fe-border);
4806
+ }
4807
+ .fe-sidenav--rail .fe-sidenav__quota {
4808
+ padding: 10px 8px;
4809
+ }
4810
+ .fe-sidenav__quota-label {
4811
+ margin: 0 0 6px;
4812
+ font-size: 11px;
4813
+ font-weight: 600;
4814
+ letter-spacing: 0.04em;
4815
+ text-transform: uppercase;
4816
+ color: var(--fe-text-muted);
4817
+ }
4818
+ .fe-sidenav__quota-bar {
4819
+ height: 5px;
4820
+ border-radius: 999px;
4821
+ background: var(--fe-bg-hover);
4822
+ overflow: hidden;
4823
+ }
4824
+ .fe-sidenav__quota-fill {
4825
+ display: block;
4826
+ height: 100%;
4827
+ border-radius: 999px;
4828
+ background: var(--fe-primary);
4829
+ transition: width 0.2s ease;
4830
+ }
4831
+ .fe-sidenav__quota-text {
4832
+ margin: 6px 0 0;
4833
+ font-size: 12px;
4834
+ color: var(--fe-text-muted);
4835
+ }
4836
+
4837
+ /* ── the breadcrumb row ───────────────────────────────────────────────── */
4838
+ /* Not a drive shell: the wrapper leaves layout entirely, so the breadcrumb is
4839
+ the same flex child of .fe__primary it has always been. */
4840
+ .fe-subhead--plain {
4841
+ display: contents;
4842
+ }
4843
+ .fe-subhead {
4844
+ display: flex;
4845
+ align-items: center;
4846
+ gap: 8px;
4847
+ min-width: 0;
4848
+ }
4849
+ .fe-subhead > .fe-breadcrumb {
4850
+ flex: 1 1 auto;
4851
+ min-width: 0;
4852
+ }
4853
+ .fe-subhead__actions {
4854
+ flex: 0 0 auto;
4855
+ display: flex;
4856
+ align-items: center;
4857
+ gap: 6px;
4858
+ padding-right: 8px;
4859
+ }
4860
+
4861
+ /* ── the filter row ───────────────────────────────────────────────────── */
4862
+ .fe-filterbar {
4863
+ flex: 0 0 auto;
4864
+ display: flex;
4865
+ align-items: center;
4866
+ gap: 8px;
4867
+ padding: 8px 10px;
4868
+ /* Scrolls sideways ON ITSELF at 390px rather than widening the explorer:
4869
+ three chips and a clear button do not fit a phone, and the alternative is
4870
+ a horizontal scrollbar on everything. */
4871
+ overflow-x: auto;
4872
+ overflow-y: hidden;
4873
+ scrollbar-width: none;
4874
+ }
4875
+ .fe-filterbar::-webkit-scrollbar {
4876
+ display: none;
4877
+ }
4878
+ .fe-filterbar__chip {
4879
+ flex: 0 0 auto;
4880
+ display: inline-flex;
4881
+ align-items: center;
4882
+ gap: 4px;
4883
+ height: 32px;
4884
+ padding: 0 10px 0 12px;
4885
+ border: 1px solid var(--fe-border-strong);
4886
+ border-radius: 999px;
4887
+ background: transparent;
4888
+ color: var(--fe-text);
4889
+ font: inherit;
4890
+ font-size: 13px;
4891
+ cursor: pointer;
4892
+ white-space: nowrap;
4893
+ }
4894
+ .fe-filterbar__chip:hover {
4895
+ background: var(--fe-bg-hover);
4896
+ }
4897
+ .fe-filterbar__chip.is-set {
4898
+ background: var(--fe-bg-selected);
4899
+ border-color: var(--fe-primary);
4900
+ font-weight: 600;
4901
+ }
4902
+ .fe-filterbar__chip.is-open {
4903
+ border-color: var(--fe-primary);
4904
+ }
4905
+ .fe-filterbar__chip:focus-visible {
4906
+ outline: 2px solid var(--fe-primary);
4907
+ outline-offset: 1px;
4908
+ }
4909
+ .fe-filterbar__caret {
4910
+ width: 15px;
4911
+ height: 15px;
4912
+ }
4913
+ .fe-filterbar__clear {
4914
+ flex: 0 0 auto;
4915
+ border: 0;
4916
+ background: transparent;
4917
+ color: var(--fe-primary);
4918
+ font: inherit;
4919
+ font-size: 12.5px;
4920
+ cursor: pointer;
4921
+ padding: 4px 6px;
4922
+ border-radius: var(--fe-radius-sm);
4923
+ white-space: nowrap;
4924
+ }
4925
+ .fe-filterbar__clear:hover {
4926
+ background: var(--fe-bg-hover);
4927
+ }
4928
+ .fe-filterbar__count {
4929
+ flex: 0 0 auto;
4930
+ margin-left: auto;
4931
+ padding-left: 8px;
4932
+ font-size: 12px;
4933
+ color: var(--fe-text-muted);
4934
+ white-space: nowrap;
4935
+ }
4936
+ /* Teleported to <body>: z-index 90, the layer the context menus already use.
4937
+ The onboarding tour sits above at 96 on purpose — a tour step must never end
4938
+ up behind a filter popover. */
4939
+ .fe-filterpop {
4940
+ position: fixed;
4941
+ z-index: 90;
4942
+ min-width: 190px;
4943
+ max-height: 60vh;
4944
+ overflow-y: auto;
4945
+ padding: 6px;
4946
+ border: 1px solid var(--fe-border);
4947
+ border-radius: var(--fe-radius-md);
4948
+ background: var(--fe-bg);
4949
+ color: var(--fe-text);
4950
+ box-shadow: var(--fe-shadow);
4951
+ font-family: var(--fe-font);
4952
+ }
4953
+ .fe-filterpop__item {
4954
+ display: flex;
4955
+ align-items: center;
4956
+ gap: 8px;
4957
+ width: 100%;
4958
+ padding: 7px 10px;
4959
+ border: 0;
4960
+ border-radius: var(--fe-radius-sm);
4961
+ background: transparent;
4962
+ color: inherit;
4963
+ font: inherit;
4964
+ font-size: 13px;
4965
+ text-align: left;
4966
+ cursor: pointer;
4967
+ }
4968
+ .fe-filterpop__item:hover {
4969
+ background: var(--fe-bg-hover);
4970
+ }
4971
+ .fe-filterpop__item:focus-visible {
4972
+ outline: 2px solid var(--fe-primary);
4973
+ outline-offset: -2px;
4974
+ }
4975
+ .fe-filterpop__tick {
4976
+ flex: 0 0 14px;
4977
+ color: var(--fe-primary);
4978
+ font-size: 12px;
4979
+ }
4980
+
4981
+ /* ── Folders / Files sections in the grid ─────────────────────────────── */
4982
+ /* A grid item spanning every column, so the cards keep flowing under it. */
4983
+ .fe-grid__heading {
4984
+ grid-column: 1 / -1;
4985
+ margin: 6px 2px 2px;
4986
+ font-size: 13px;
4987
+ font-weight: 600;
4988
+ color: var(--fe-text);
4989
+ }
4990
+ .fe-grid__heading:first-child {
4991
+ margin-top: 0;
4992
+ }
4993
+
4994
+ /* ── the details panel: tabs, people, link row ────────────────────────── */
4995
+ .fe-inspector__tabs {
4996
+ flex: 0 0 auto;
4997
+ display: flex;
4998
+ gap: 2px;
4999
+ padding: 0 10px;
5000
+ border-bottom: 1px solid var(--fe-border);
5001
+ }
5002
+ .fe-inspector__tab {
5003
+ padding: 9px 12px;
5004
+ border: 0;
5005
+ border-bottom: 2px solid transparent;
5006
+ background: transparent;
5007
+ color: var(--fe-text-muted);
5008
+ font: inherit;
5009
+ font-size: 13px;
5010
+ cursor: pointer;
5011
+ }
5012
+ .fe-inspector__tab:hover {
5013
+ color: var(--fe-text);
5014
+ }
5015
+ .fe-inspector__tab.is-active {
5016
+ color: var(--fe-primary);
5017
+ border-bottom-color: var(--fe-primary);
5018
+ font-weight: 600;
5019
+ }
5020
+ .fe-inspector__tab:focus-visible {
5021
+ outline: 2px solid var(--fe-primary);
5022
+ outline-offset: -2px;
5023
+ }
5024
+ .fe-inspector__people {
5025
+ list-style: none;
5026
+ margin: 0 0 8px;
5027
+ padding: 0;
5028
+ display: flex;
5029
+ flex-direction: column;
5030
+ gap: 8px;
5031
+ }
5032
+ .fe-inspector__person {
5033
+ display: flex;
5034
+ align-items: center;
5035
+ gap: 10px;
5036
+ min-width: 0;
5037
+ }
5038
+ .fe-inspector__avatar {
5039
+ flex: 0 0 28px;
5040
+ width: 28px;
5041
+ height: 28px;
5042
+ border-radius: 50%;
5043
+ display: inline-flex;
5044
+ align-items: center;
5045
+ justify-content: center;
5046
+ background: var(--fe-primary);
5047
+ color: var(--fe-text-on-primary);
5048
+ font-size: 12px;
5049
+ font-weight: 600;
5050
+ }
5051
+ .fe-inspector__person-main {
5052
+ display: flex;
5053
+ flex-direction: column;
5054
+ min-width: 0;
5055
+ }
5056
+ .fe-inspector__person-name {
5057
+ font-size: 13px;
5058
+ overflow: hidden;
5059
+ text-overflow: ellipsis;
5060
+ white-space: nowrap;
5061
+ }
5062
+ .fe-inspector__person-sub {
5063
+ font-size: 11.5px;
5064
+ color: var(--fe-text-muted);
5065
+ }
5066
+ .fe-inspector__linkrow {
5067
+ display: flex;
5068
+ align-items: center;
5069
+ gap: 8px;
5070
+ }
5071
+ .fe-inspector__linkicon {
5072
+ flex: 0 0 auto;
5073
+ font-size: 14px;
5074
+ }
5075
+ .fe-inspector__linknone {
5076
+ flex: 1 1 auto;
5077
+ min-width: 0;
5078
+ font-size: 13px;
5079
+ color: var(--fe-text-muted);
5080
+ }
5081
+ .fe-inspector__hint {
5082
+ margin: 6px 0 0;
5083
+ font-size: 12px;
5084
+ color: var(--fe-text-muted);
5085
+ }
5086
+
5087
+ /* ── narrow ───────────────────────────────────────────────────────────── */
5088
+ /* At 390px the chip is the only thing that has to go: the shortcut it prints
5089
+ cannot be typed on a phone anyway, and the palette stays reachable from the
5090
+ "⋯" menu. The field keeps the whole row. */
5091
+ .fe--narrow .fe-drivesearch__kbd {
5092
+ display: none;
5093
+ }
5094
+ .fe--narrow .fe-drivesearch {
5095
+ height: 36px;
5096
+ max-width: none;
5097
+ }
5098
+ .fe--narrow .fe-subhead__actions {
5099
+ padding-right: 4px;
5100
+ }
5101
+ .fe--narrow .fe-filterbar {
5102
+ padding: 6px 8px;
5103
+ }
5104
+ /* The narrow DRIVE header is three items: toggle · field · "⋯". The field is
5105
+ the only one that grows, and `min-width: 0` is what lets it shrink past its
5106
+ own content width — without it the row is wider than the phone. */
5107
+ .fe-toolbar--narrow.fe-toolbar--drive {
5108
+ gap: 6px;
5109
+ padding: 6px 8px;
5110
+ }
5111
+ .fe-toolbar--narrow .fe-drivesearch {
5112
+ flex: 1 1 auto;
5113
+ min-width: 0;
5114
+ }
5115
+ /* === /surucu:d1 === */
@@ -277,6 +277,22 @@ export interface ExplorerConfig {
277
277
  * navigation panel starts expanded, the tab strip
278
278
  * and split pane are off, the gallery view mode and
279
279
  * the host's "How to connect" surface are hidden.
280
+ * 'drive' — everything `simple` does, plus the shell an end
281
+ * user already knows: one primary "New" menu, one
282
+ * search field in the header (with its ⌘K/Ctrl+K
283
+ * escalation into the command palette), a filter row
284
+ * under the breadcrumb, Folders and Files as
285
+ * labelled sections in grid view, the details panel
286
+ * split into Details / Activity, and a storage line
287
+ * under the navigation.
288
+ *
289
+ * ⚠ `drive` is a SUPERSET of `simple`, not a sibling: everything `simple`
290
+ * turns off stays off, and the code asks `simpleUi` for those questions so a
291
+ * later change to `simple` cannot silently miss `drive`. It is a third value
292
+ * rather than a second boolean because "which chrome" is ONE question with
293
+ * three answers — a `driveShell: true` next to `uiProfile: 'standard'` would
294
+ * be a combination nobody can describe, and keeping that question single is
295
+ * why `uiProfile` was a preset to begin with.
280
296
  *
281
297
  * Why it exists (GitHub #14): the reporter's users are not in IT and read
282
298
  * split panes, tabs and mount instructions as a file manager they would have
@@ -288,7 +304,7 @@ export interface ExplorerConfig {
288
304
  * rest of the chrome differ. A viewer's own collapse choice, once made,
289
305
  * outranks the profile — it is a per-viewer preference, not a policy.
290
306
  */
291
- uiProfile?: 'standard' | 'simple';
307
+ uiProfile?: 'standard' | 'simple' | 'drive';
292
308
 
293
309
  /**
294
310
  * Render the navigation panel (Upload · Recent / Starred / Shared with me /