dsh-workspace-ide 0.0.0-stage → 1.8.1
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.
- package/LICENSE +21 -0
- package/README.md +249 -2
- package/THIRD_PARTY_NOTICES.md +19 -0
- package/cordis.patch.yml +8 -0
- package/icon.svg +8 -0
- package/lib/client.js +11332 -0
- package/lib/host/file.js +242 -0
- package/lib/host/files.js +82 -0
- package/lib/host/git.js +467 -0
- package/lib/host/image.js +166 -0
- package/lib/host/paths.js +211 -0
- package/lib/host/respond.js +210 -0
- package/lib/host/routes.js +126 -0
- package/lib/host/search.js +330 -0
- package/lib/host/status.js +111 -0
- package/lib/host/tree.js +177 -0
- package/lib/host/workspaces.js +114 -0
- package/lib/index.js +36 -0
- package/licenses/lucide-LICENSE.txt +43 -0
- package/package.json +79 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 echoVic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,250 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-workspace-ide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/echoVic/dsh-workspace-ide/actions/workflows/test.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
A VS Code-like workspace panel plugin for
|
|
7
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): browse the
|
|
8
|
+
workspace, search it, and read its git state without leaving the conversation.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
| Surface | What it does |
|
|
13
|
+
|---|---|
|
|
14
|
+
| **Files** | Lazy directory tree for the whole workspace, resizable split, line-numbered **syntax-highlighted** code viewer with **git change marks in the gutter**, **clickable breadcrumbs**, **soft-wrap** (`Alt+Z`), **go-to-line** (`Ctrl/Cmd+G`), **in-file find & replace** (`Ctrl/Cmd+F` / `Ctrl/Cmd+H`, case/whole-word/regex, all-match highlight), **quick open** (`Ctrl/Cmd+P`), **multi-tab editing** with per-tab drafts and unsaved-change guards (`Ctrl/Cmd+W` closes), **back/forward navigation** (`Ctrl/Cmd+Alt+←/→`) a JetBrains-style status bar and **editor zoom** (`Ctrl/Cmd+=` / `-`, `Ctrl/Cmd+0` resets), inline editing with `Ctrl/Cmd+S` save, selection-aware `Tab` indentation, `Ctrl/Cmd+D` duplicate-line, `Alt+Shift+↑/↓` move-line, `Ctrl/Cmd+Shift+T` reopen-closed-tab, **bracket matching / auto-pair / smart indent / indent guides / current-line highlight** and `Cmd/Ctrl+/` comment toggling, rendered **Markdown preview** with source toggle, **image / SVG preview**, large-file/binary guards |
|
|
15
|
+
| **Search** | Full-text workspace search with case / regex / whole-word toggles and an include filter; hits are grouped per file, highlighted, and jump straight to the matching line in the viewer |
|
|
16
|
+
| **Git** | Current branch with ahead/behind, staged / unstaged / untracked / conflicted groups with colourised unified diffs, and a commit history with per-commit diffs; a non-git workspace lists other git workspaces for one-click switching |
|
|
17
|
+
|
|
18
|
+
### Colour themes
|
|
19
|
+
|
|
20
|
+
The panel ships the mainstream VS Code palettes and switches between them without touching
|
|
21
|
+
the rest of the DSH UI:
|
|
22
|
+
|
|
23
|
+
| Selection | Palette |
|
|
24
|
+
|---|---|
|
|
25
|
+
| **Follow DSH theme** (default) | the live `--dsw-alias-*` values, so it matches the app light/dark theme |
|
|
26
|
+
| **Dark+** | VS Code's default dark palette |
|
|
27
|
+
| **Light+** | VS Code's default light palette |
|
|
28
|
+
| **One Dark Pro** | Atom's One Dark |
|
|
29
|
+
| **Dracula** | the official Dracula palette |
|
|
30
|
+
| **GitHub Dark** | GitHub's dark palette |
|
|
31
|
+
| **Solarized Light** | Ethan Schoonover's Solarized Light |
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
> All screenshots in this README are rendered from a **synthetic demo workspace**
|
|
36
|
+
> (`aurora-dashboard`) with mock files, git history and diffs — never real user data.
|
|
37
|
+
> They are generated reproducibly by
|
|
38
|
+
> [`scripts/screenshots/build-screenshots.py`](scripts/screenshots/build-screenshots.py),
|
|
39
|
+
> which mounts the real client bundle against a stubbed fetch layer.
|
|
40
|
+
|
|
41
|
+
Each theme is a table of `--dsw-alias-*` custom-property overrides scoped to the panel root;
|
|
42
|
+
every surface (tree, editor gutter, search highlights, git diff colours, scrollbars) follows
|
|
43
|
+
the selection, and the choice is remembered in `localStorage`. The toolbar glyphs are
|
|
44
|
+
borderless — an inactive tab is just the icon, the active tab is a rounded background, and
|
|
45
|
+
the focus ring appears only for keyboard navigation.
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
### Syntax highlighting
|
|
51
|
+
|
|
52
|
+
The editor colours code with a small, dependency-free highlighter written for this panel —
|
|
53
|
+
no Shiki/Prism bundle and no extra runtime dependency (the whole browser half still only
|
|
54
|
+
requires `react`). It covers every language the Host reports: JavaScript / TypeScript /
|
|
55
|
+
JSX / TSX, JSON, Markdown, CSS, HTML/XML, Python, Go, Rust, Java, Kotlin, Swift, C/C++, C#,
|
|
56
|
+
Ruby, PHP, Shell, PowerShell, YAML, TOML, INI, SQL, GraphQL and the tag level of
|
|
57
|
+
Vue/Svelte; unknown files and files over 300 KB fall back to plain text for rendering speed.
|
|
58
|
+
|
|
59
|
+
Token colours follow the selected palette: **Follow DSH theme** inherits the app's own
|
|
60
|
+
`--shiki-token-*` colours (so it matches the surrounding light/dark UI), and each VSCode
|
|
61
|
+
theme ships its own keyword / string / comment / number / function / type / variable table.
|
|
62
|
+
|
|
63
|
+
The editor stays editable while highlighted: a transparent-text `textarea` sits over the
|
|
64
|
+
syntax-coloured layer, with caret, selection, both scroll axes and the line-number gutter
|
|
65
|
+
kept in sync. Read-only previews reuse the same coloured rendering.
|
|
66
|
+
|
|
67
|
+
Editing keeps the VSCode muscle memory: **`Cmd/Ctrl+/` toggles comments** for the current
|
|
68
|
+
line or every selected line (the per-language token table covers `//`, `#`, `--`, `;` and
|
|
69
|
+
`/* */` / `<!-- -->` block wrapping for CSS, HTML, XML, Markdown, Vue and Svelte), and the
|
|
70
|
+
toggle is reversible, indent-preserving and leaves blank lines untouched.
|
|
71
|
+
|
|
72
|
+
### Editor: JetBrains-style workflow
|
|
73
|
+
|
|
74
|
+
Beyond editing and saving, the viewer follows the interactions JetBrains IDEs made
|
|
75
|
+
standard:
|
|
76
|
+
|
|
77
|
+
- **Find & replace** (`Ctrl/Cmd+F` / `Ctrl/Cmd+H`) with match counter, previous/next,
|
|
78
|
+
case / whole-word / regex toggles, Replace and Replace All, and a highlight layer that
|
|
79
|
+
paints every match behind the text.
|
|
80
|
+
- **Quick Open** (`Ctrl/Cmd+P`) ranks the whole workspace file index locally with a fuzzy
|
|
81
|
+
subsequence score; **Go to Symbol** (`Ctrl/Cmd+Shift+O`) jumps to functions, classes,
|
|
82
|
+
types, constants and (in Markdown/JSON) headings or keys in the current file
|
|
83
|
+
(`Alt+↑/↓` walks symbols; the header shows the enclosing symbol);
|
|
84
|
+
**Recent Locations** (`Ctrl/Cmd+Shift+E`) reopens any earlier file:line,
|
|
85
|
+
**bookmarks** (`F11` / `Shift+F11`) mark and revisit lines, and **case toggle** /
|
|
86
|
+
**join lines** round out the editing shortcuts.
|
|
87
|
+
- **Tabs** keep one tab per open file with dirty markers; unsaved drafts and carets survive
|
|
88
|
+
tab switches, and closing a dirty tab asks before discarding.
|
|
89
|
+
- **Bracket matching / auto-pair / smart indent / current line / indent guides** make the
|
|
90
|
+
same gestures feel native: matched brackets highlight, openers auto-close, Enter inherits
|
|
91
|
+
indentation, and Shift+Tab outdents.
|
|
92
|
+
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+

|
|
96
|
+
|
|
97
|
+

|
|
98
|
+
|
|
99
|
+

|
|
100
|
+
|
|
101
|
+
### Markdown preview
|
|
102
|
+
|
|
103
|
+
`.md` files switch between editable source and a rendered preview from the file header, or
|
|
104
|
+
with `Cmd/Ctrl+Shift+V`. The preview is built from React elements — never injected HTML — and
|
|
105
|
+
covers headings, fenced code blocks (highlighted by the same engine, in the selected theme),
|
|
106
|
+
ordered/unordered and task lists, blockquotes, GFM tables, links, images and rules;
|
|
107
|
+
`javascript:`/`data:` URLs are dropped. It renders the current draft, so unsaved edits
|
|
108
|
+
preview immediately, and read-only/oversized Markdown files still preview.
|
|
109
|
+
|
|
110
|
+

|
|
111
|
+
|
|
112
|
+
### Images and SVG
|
|
113
|
+
|
|
114
|
+
Raster images (PNG, JPEG, GIF, WebP, BMP, ICO, AVIF) open in a centred preview over a
|
|
115
|
+
checkerboard surface, with the file's natural dimensions and size in the status bar. SVGs
|
|
116
|
+
get both a rendered preview and editable source, switchable with the `图片 / 源码` toggle or
|
|
117
|
+
`Cmd/Ctrl+Shift+V`; the preview is rendered from the current source, so editing the SVG
|
|
118
|
+
updates the picture immediately. Image bytes come from a dedicated Host route that verifies
|
|
119
|
+
every file against its magic number and caps reads at 8 MiB.
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+

|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
The panel opens from the left rail (**工作区 / Workspace**, next to *Plugins*, *Scheduled
|
|
127
|
+
tasks*, *Usage stats*). The three-icon strip at the top switches between Files / Search /
|
|
128
|
+
Git — the file-copy, list-search and git-branch activity icons. The workspace picker on
|
|
129
|
+
the right follows the session the app is currently viewing, and a manual pick sticks for
|
|
130
|
+
that session.
|
|
131
|
+
|
|
132
|
+

|
|
133
|
+
|
|
134
|
+

|
|
135
|
+
|
|
136
|
+

|
|
137
|
+
|
|
138
|
+
## Install
|
|
139
|
+
|
|
140
|
+
From a DSH session, install the bundle from this directory with the plugin manager
|
|
141
|
+
(absolute path required):
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
plugin_manager {
|
|
145
|
+
action: "install_bundle",
|
|
146
|
+
target: "/absolute/path/to/dsh-workspace-ide"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The bundle is profile-wide and survives restarts. `application: "applied"` in the result
|
|
151
|
+
means it activated immediately; client-side changes are picked up by DSH's client HMR,
|
|
152
|
+
host-side changes load on the next Host start.
|
|
153
|
+
|
|
154
|
+
Or install straight from GitHub instead of a local checkout:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
plugin_manager { action: "install_bundle", target: "echoVic/dsh-workspace-ide" }
|
|
158
|
+
# or pin a release tarball:
|
|
159
|
+
plugin_manager { action: "install_bundle", target: "https://github.com/echoVic/dsh-workspace-ide/archive/refs/tags/v1.0.0.tar.gz" }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## How it works
|
|
163
|
+
|
|
164
|
+
- **Host half** (`lib/index.js`, `lib/host/*.js`): registers nine authenticated fetch
|
|
165
|
+
routes through `ctx.connection.fetch.register` — `workspaces`, `list`, `files`, `file`,
|
|
166
|
+
`image`, `search`, `git`, `save`, `ping`. Filesystem access is path-checked (absolute paths, `..` escapes,
|
|
167
|
+
symlink escapes and roots outside known workspaces are rejected); everything is
|
|
168
|
+
read-only except `save` (atomic overwrite of an existing regular file only) and `ping`
|
|
169
|
+
(one diagnostic line in `$DSH_HOME/workspace-ide/status.jsonl`, used by the client to
|
|
170
|
+
prove it executed). Git runs through `child_process.execFile` with bounded output; a
|
|
171
|
+
non-repository root is a normal answer, not an error.
|
|
172
|
+
- **Client half** (`lib/client.js`, generated from `src/client/*.js` + compiled
|
|
173
|
+
TypeScript fragments by `scripts/build-client.mjs`): one `window.__ModuleLoader__.load(...)` browser module that
|
|
174
|
+
registers a `sidebar.panellist` icon and the matching keyed `main` panel. It only uses
|
|
175
|
+
`require('react')`, the `--dsw-alias-*` theme tokens (dark and light), a built-in zh/en
|
|
176
|
+
dictionary, and the DSH frame's `--dsh-frame-*` chrome insets; the page is wrapped in an
|
|
177
|
+
error boundary. The panel follows the viewed session's workspace via the slot kit's
|
|
178
|
+
`useWorkspaces` hook, and falls back safely when that hook is absent.
|
|
179
|
+
- **Icons**: the whole toolbar/tree/git/theme icon set is the open-source
|
|
180
|
+
[Lucide](https://lucide.dev) set (ISC, with Feather-derived glyphs under MIT). Its SVG
|
|
181
|
+
node data is vendored at authoring time in `src/client/05-icons.ts` — no runtime icon
|
|
182
|
+
dependency — and renders through one factory so every glyph shares the 24px grid, round
|
|
183
|
+
caps and `currentColor` stroke. Licenses: [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md),
|
|
184
|
+
[`licenses/lucide-LICENSE.txt`](licenses/lucide-LICENSE.txt).
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npm ci # dev-only tooling: TypeScript type checker
|
|
190
|
+
npm run typecheck # tsc --checkJs, strict, over lib/ + src/client + scripts + tests (no emit)
|
|
191
|
+
npm run typecheck:host # lib/ additionally under noImplicitAny (see tsconfig.host.json)
|
|
192
|
+
npm run typecheck:client # all client sources under noImplicitAny (tsconfig.client.json)
|
|
193
|
+
node scripts/build-client.mjs # rebuild lib/client.js from src/client/ (.js + compiled .ts)
|
|
194
|
+
node scripts/build-client.mjs --check # fail when lib/client.js is stale
|
|
195
|
+
node --check lib/client.js
|
|
196
|
+
node --test test/host/ # Host route unit tests (node:test, 118)
|
|
197
|
+
node test/smoke/client-smoke.mjs # headless client module/face smoke test (43 checks)
|
|
198
|
+
node --test test/review/*.test.mjs # independent adversarial + contract suites (78)
|
|
199
|
+
python3 scripts/screenshots/build-screenshots.py # rebuild docs/ screenshots (mock data)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
TypeScript is used **only as a dev-time type checker**: sources stay plain JavaScript with
|
|
203
|
+
JSDoc annotations, and `tsc --noEmit --checkJs` checks `lib/`, `src/client/`, `scripts/` and
|
|
204
|
+
the tests with `strict: true` (the one deferred strict flag is `noImplicitAny`, see
|
|
205
|
+
`CONTRACT.md`). `types/client-globals.d.ts` declares the shared factory scope and the React
|
|
206
|
+
element alias; `types/dsh-services.d.ts` declares the optional Host context/services.
|
|
207
|
+
There is still no client bundler, no build step for the Host and no runtime dependency —
|
|
208
|
+
`npm ci` installs `typescript`, `@types/node` and `@types/react` into devDependencies only.
|
|
209
|
+
|
|
210
|
+
The client half is authored in TypeScript: script-style fragments in
|
|
211
|
+
`src/client/*.ts` compiled by `tsc` into `build/client-parts/` (never shipped,
|
|
212
|
+
git-ignored) and concatenated into one plain script — still no bundler, and
|
|
213
|
+
`lib/client.js` keeps its exact loader contract.
|
|
214
|
+
|
|
215
|
+
`lib/client.js` is served directly by the Host and DSH's client HMR watches installed
|
|
216
|
+
bundle artifacts, so saving a rebuilt `lib/client.js` updates an open Harness page without
|
|
217
|
+
a restart.
|
|
218
|
+
|
|
219
|
+
## Layout
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
lib/index.js Host plugin entry (`apply`, `inject`, `name`)
|
|
223
|
+
lib/host/ filesystem / search / git route implementation
|
|
224
|
+
lib/client.js built browser module (committed)
|
|
225
|
+
tsconfig.json dev-only type checker config (allowJs + checkJs, strict, no emit)
|
|
226
|
+
tsconfig.host.json Host-only config adding noImplicitAny
|
|
227
|
+
tsconfig.client.json all client sources under noImplicitAny
|
|
228
|
+
types/ ambient declarations: client factory scope + Host context/services
|
|
229
|
+
src/client/ browser sources (TS): core, icons (vendored Lucide), highlighter,
|
|
230
|
+
shell, files, search, git, module face
|
|
231
|
+
THIRD_PARTY_NOTICES.md icon license attribution
|
|
232
|
+
licenses/ full third-party license texts
|
|
233
|
+
scripts/build-client.mjs concatenating build + staleness check
|
|
234
|
+
test/host/ node:test suites (122 tests)
|
|
235
|
+
test/smoke/ headless client-module smoke test (43 checks)
|
|
236
|
+
test/review/ independent adversarial + client-contract suites (78 tests)
|
|
237
|
+
docs/ screenshots
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Changelog
|
|
241
|
+
|
|
242
|
+
Release history and feature milestones: [CHANGELOG.md](CHANGELOG.md).
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
[MIT](LICENSE) © 2026 echoVic.
|
|
247
|
+
|
|
248
|
+
The vendored Lucide icon set (and the Feather-derived glyphs inside it) keep their own
|
|
249
|
+
licenses — full texts and attribution are in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
|
|
250
|
+
and [licenses/](licenses/).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## Lucide icons
|
|
4
|
+
|
|
5
|
+
`src/client/05-icons.js` vendors the SVG node data of the following
|
|
6
|
+
[Lucide](https://lucide.dev) icons (lucide-static v0.460.0) so the client bundle
|
|
7
|
+
stays a single plain-JavaScript module with `react` as its only runtime import:
|
|
8
|
+
|
|
9
|
+
`files`, `text-search`, `git-branch`, `folder`, `file`, `chevron-right`,
|
|
10
|
+
`refresh-cw`, `save`, `x`, `triangle-alert`, `lock`, `circle`,
|
|
11
|
+
`loader-circle`, `folder-tree`, `palette`, `check`
|
|
12
|
+
|
|
13
|
+
Lucide is licensed under the ISC License; the icons listed as derived from the
|
|
14
|
+
Feather project (`chevron-right`, `circle`, `lock`, `x`, …) additionally carry
|
|
15
|
+
the Feather MIT License. The full license texts ship in
|
|
16
|
+
[`licenses/lucide-LICENSE.txt`](licenses/lucide-LICENSE.txt).
|
|
17
|
+
|
|
18
|
+
The plugin's own layout, styles and remaining glyph composition are original to
|
|
19
|
+
this package (`dsh-workspace-ide`, MIT).
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Bundle patch layer for dsh-workspace-ide.
|
|
2
|
+
#
|
|
3
|
+
# One row inserts this package into the profile's Loader tree. The row `name`
|
|
4
|
+
# is the module specifier, which is also what the client module scan keys on —
|
|
5
|
+
# it must equal the package name in package.json.
|
|
6
|
+
- insert:
|
|
7
|
+
- id: workspace-ide
|
|
8
|
+
name: 'dsh-workspace-ide'
|
package/icon.svg
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none">
|
|
2
|
+
<g stroke="#7aa2f7" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" />
|
|
4
|
+
<path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" />
|
|
5
|
+
<path d="M3 5a2 2 0 0 0 2 2h3" />
|
|
6
|
+
<path d="M3 3v13a2 2 0 0 0 2 2h3" />
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|