code.gitea.io/gitea@v1.22.3/web_src/js/svg.js (about) 1 import {h} from 'vue'; 2 import {parseDom, serializeXml} from './utils.js'; 3 import giteaDoubleChevronLeft from '../../public/assets/img/svg/gitea-double-chevron-left.svg'; 4 import giteaDoubleChevronRight from '../../public/assets/img/svg/gitea-double-chevron-right.svg'; 5 import giteaEmptyCheckbox from '../../public/assets/img/svg/gitea-empty-checkbox.svg'; 6 import giteaExclamation from '../../public/assets/img/svg/gitea-exclamation.svg'; 7 import octiconArchive from '../../public/assets/img/svg/octicon-archive.svg'; 8 import octiconArrowSwitch from '../../public/assets/img/svg/octicon-arrow-switch.svg'; 9 import octiconBlocked from '../../public/assets/img/svg/octicon-blocked.svg'; 10 import octiconBold from '../../public/assets/img/svg/octicon-bold.svg'; 11 import octiconCheck from '../../public/assets/img/svg/octicon-check.svg'; 12 import octiconCheckbox from '../../public/assets/img/svg/octicon-checkbox.svg'; 13 import octiconCheckCircleFill from '../../public/assets/img/svg/octicon-check-circle-fill.svg'; 14 import octiconChevronDown from '../../public/assets/img/svg/octicon-chevron-down.svg'; 15 import octiconChevronLeft from '../../public/assets/img/svg/octicon-chevron-left.svg'; 16 import octiconChevronRight from '../../public/assets/img/svg/octicon-chevron-right.svg'; 17 import octiconClock from '../../public/assets/img/svg/octicon-clock.svg'; 18 import octiconCode from '../../public/assets/img/svg/octicon-code.svg'; 19 import octiconColumns from '../../public/assets/img/svg/octicon-columns.svg'; 20 import octiconCopy from '../../public/assets/img/svg/octicon-copy.svg'; 21 import octiconDiffAdded from '../../public/assets/img/svg/octicon-diff-added.svg'; 22 import octiconDiffModified from '../../public/assets/img/svg/octicon-diff-modified.svg'; 23 import octiconDiffRemoved from '../../public/assets/img/svg/octicon-diff-removed.svg'; 24 import octiconDiffRenamed from '../../public/assets/img/svg/octicon-diff-renamed.svg'; 25 import octiconDotFill from '../../public/assets/img/svg/octicon-dot-fill.svg'; 26 import octiconDownload from '../../public/assets/img/svg/octicon-download.svg'; 27 import octiconEye from '../../public/assets/img/svg/octicon-eye.svg'; 28 import octiconFile from '../../public/assets/img/svg/octicon-file.svg'; 29 import octiconFileDirectoryFill from '../../public/assets/img/svg/octicon-file-directory-fill.svg'; 30 import octiconFilter from '../../public/assets/img/svg/octicon-filter.svg'; 31 import octiconGear from '../../public/assets/img/svg/octicon-gear.svg'; 32 import octiconGitBranch from '../../public/assets/img/svg/octicon-git-branch.svg'; 33 import octiconGitCommit from '../../public/assets/img/svg/octicon-git-commit.svg'; 34 import octiconGitMerge from '../../public/assets/img/svg/octicon-git-merge.svg'; 35 import octiconGitPullRequest from '../../public/assets/img/svg/octicon-git-pull-request.svg'; 36 import octiconGitPullRequestDraft from '../../public/assets/img/svg/octicon-git-pull-request-draft.svg'; 37 import octiconHeading from '../../public/assets/img/svg/octicon-heading.svg'; 38 import octiconHorizontalRule from '../../public/assets/img/svg/octicon-horizontal-rule.svg'; 39 import octiconImage from '../../public/assets/img/svg/octicon-image.svg'; 40 import octiconIssueClosed from '../../public/assets/img/svg/octicon-issue-closed.svg'; 41 import octiconIssueOpened from '../../public/assets/img/svg/octicon-issue-opened.svg'; 42 import octiconItalic from '../../public/assets/img/svg/octicon-italic.svg'; 43 import octiconKebabHorizontal from '../../public/assets/img/svg/octicon-kebab-horizontal.svg'; 44 import octiconLink from '../../public/assets/img/svg/octicon-link.svg'; 45 import octiconListOrdered from '../../public/assets/img/svg/octicon-list-ordered.svg'; 46 import octiconListUnordered from '../../public/assets/img/svg/octicon-list-unordered.svg'; 47 import octiconLock from '../../public/assets/img/svg/octicon-lock.svg'; 48 import octiconMeter from '../../public/assets/img/svg/octicon-meter.svg'; 49 import octiconMilestone from '../../public/assets/img/svg/octicon-milestone.svg'; 50 import octiconMirror from '../../public/assets/img/svg/octicon-mirror.svg'; 51 import octiconOrganization from '../../public/assets/img/svg/octicon-organization.svg'; 52 import octiconPlay from '../../public/assets/img/svg/octicon-play.svg'; 53 import octiconPlus from '../../public/assets/img/svg/octicon-plus.svg'; 54 import octiconProject from '../../public/assets/img/svg/octicon-project.svg'; 55 import octiconQuote from '../../public/assets/img/svg/octicon-quote.svg'; 56 import octiconRepo from '../../public/assets/img/svg/octicon-repo.svg'; 57 import octiconRepoForked from '../../public/assets/img/svg/octicon-repo-forked.svg'; 58 import octiconRepoTemplate from '../../public/assets/img/svg/octicon-repo-template.svg'; 59 import octiconRss from '../../public/assets/img/svg/octicon-rss.svg'; 60 import octiconScreenFull from '../../public/assets/img/svg/octicon-screen-full.svg'; 61 import octiconSearch from '../../public/assets/img/svg/octicon-search.svg'; 62 import octiconSidebarCollapse from '../../public/assets/img/svg/octicon-sidebar-collapse.svg'; 63 import octiconSidebarExpand from '../../public/assets/img/svg/octicon-sidebar-expand.svg'; 64 import octiconSkip from '../../public/assets/img/svg/octicon-skip.svg'; 65 import octiconStar from '../../public/assets/img/svg/octicon-star.svg'; 66 import octiconStrikethrough from '../../public/assets/img/svg/octicon-strikethrough.svg'; 67 import octiconSync from '../../public/assets/img/svg/octicon-sync.svg'; 68 import octiconTable from '../../public/assets/img/svg/octicon-table.svg'; 69 import octiconTag from '../../public/assets/img/svg/octicon-tag.svg'; 70 import octiconTrash from '../../public/assets/img/svg/octicon-trash.svg'; 71 import octiconTriangleDown from '../../public/assets/img/svg/octicon-triangle-down.svg'; 72 import octiconX from '../../public/assets/img/svg/octicon-x.svg'; 73 import octiconXCircleFill from '../../public/assets/img/svg/octicon-x-circle-fill.svg'; 74 75 const svgs = { 76 'gitea-double-chevron-left': giteaDoubleChevronLeft, 77 'gitea-double-chevron-right': giteaDoubleChevronRight, 78 'gitea-empty-checkbox': giteaEmptyCheckbox, 79 'gitea-exclamation': giteaExclamation, 80 'octicon-archive': octiconArchive, 81 'octicon-arrow-switch': octiconArrowSwitch, 82 'octicon-blocked': octiconBlocked, 83 'octicon-bold': octiconBold, 84 'octicon-check': octiconCheck, 85 'octicon-check-circle-fill': octiconCheckCircleFill, 86 'octicon-checkbox': octiconCheckbox, 87 'octicon-chevron-down': octiconChevronDown, 88 'octicon-chevron-left': octiconChevronLeft, 89 'octicon-chevron-right': octiconChevronRight, 90 'octicon-clock': octiconClock, 91 'octicon-code': octiconCode, 92 'octicon-columns': octiconColumns, 93 'octicon-copy': octiconCopy, 94 'octicon-diff-added': octiconDiffAdded, 95 'octicon-diff-modified': octiconDiffModified, 96 'octicon-diff-removed': octiconDiffRemoved, 97 'octicon-diff-renamed': octiconDiffRenamed, 98 'octicon-dot-fill': octiconDotFill, 99 'octicon-download': octiconDownload, 100 'octicon-eye': octiconEye, 101 'octicon-file': octiconFile, 102 'octicon-file-directory-fill': octiconFileDirectoryFill, 103 'octicon-filter': octiconFilter, 104 'octicon-gear': octiconGear, 105 'octicon-git-branch': octiconGitBranch, 106 'octicon-git-commit': octiconGitCommit, 107 'octicon-git-merge': octiconGitMerge, 108 'octicon-git-pull-request': octiconGitPullRequest, 109 'octicon-git-pull-request-draft': octiconGitPullRequestDraft, 110 'octicon-heading': octiconHeading, 111 'octicon-horizontal-rule': octiconHorizontalRule, 112 'octicon-image': octiconImage, 113 'octicon-issue-closed': octiconIssueClosed, 114 'octicon-issue-opened': octiconIssueOpened, 115 'octicon-italic': octiconItalic, 116 'octicon-kebab-horizontal': octiconKebabHorizontal, 117 'octicon-link': octiconLink, 118 'octicon-list-ordered': octiconListOrdered, 119 'octicon-list-unordered': octiconListUnordered, 120 'octicon-lock': octiconLock, 121 'octicon-meter': octiconMeter, 122 'octicon-milestone': octiconMilestone, 123 'octicon-mirror': octiconMirror, 124 'octicon-organization': octiconOrganization, 125 'octicon-play': octiconPlay, 126 'octicon-plus': octiconPlus, 127 'octicon-project': octiconProject, 128 'octicon-quote': octiconQuote, 129 'octicon-repo': octiconRepo, 130 'octicon-repo-forked': octiconRepoForked, 131 'octicon-repo-template': octiconRepoTemplate, 132 'octicon-rss': octiconRss, 133 'octicon-screen-full': octiconScreenFull, 134 'octicon-search': octiconSearch, 135 'octicon-sidebar-collapse': octiconSidebarCollapse, 136 'octicon-sidebar-expand': octiconSidebarExpand, 137 'octicon-skip': octiconSkip, 138 'octicon-star': octiconStar, 139 'octicon-strikethrough': octiconStrikethrough, 140 'octicon-sync': octiconSync, 141 'octicon-table': octiconTable, 142 'octicon-tag': octiconTag, 143 'octicon-trash': octiconTrash, 144 'octicon-triangle-down': octiconTriangleDown, 145 'octicon-x': octiconX, 146 'octicon-x-circle-fill': octiconXCircleFill, 147 }; 148 149 // TODO: use a more general approach to access SVG icons. 150 // At the moment, developers must check, pick and fill the names manually, 151 // most of the SVG icons in assets couldn't be used directly. 152 153 // retrieve an HTML string for given SVG icon name, size and additional classes 154 export function svg(name, size = 16, className = '') { 155 if (!(name in svgs)) throw new Error(`Unknown SVG icon: ${name}`); 156 if (size === 16 && !className) return svgs[name]; 157 158 const document = parseDom(svgs[name], 'image/svg+xml'); 159 const svgNode = document.firstChild; 160 if (size !== 16) { 161 svgNode.setAttribute('width', String(size)); 162 svgNode.setAttribute('height', String(size)); 163 } 164 if (className) svgNode.classList.add(...className.split(/\s+/).filter(Boolean)); 165 return serializeXml(svgNode); 166 } 167 168 export function svgParseOuterInner(name) { 169 const svgStr = svgs[name]; 170 if (!svgStr) throw new Error(`Unknown SVG icon: ${name}`); 171 172 // parse the SVG string to 2 parts 173 // * svgInnerHtml: the inner part of the SVG, will be used as the content of the <svg> VNode 174 // * svgOuter: the outer part of the SVG, including attributes 175 // the builtin SVG contents are clean, so it's safe to use `indexOf` to split the content: 176 // eg: <svg outer-attributes>${svgInnerHtml}</svg> 177 const p1 = svgStr.indexOf('>'), p2 = svgStr.lastIndexOf('<'); 178 if (p1 === -1 || p2 === -1) throw new Error(`Invalid SVG icon: ${name}`); 179 const svgInnerHtml = svgStr.slice(p1 + 1, p2); 180 const svgOuterHtml = svgStr.slice(0, p1 + 1) + svgStr.slice(p2); 181 const svgDoc = parseDom(svgOuterHtml, 'image/svg+xml'); 182 const svgOuter = svgDoc.firstChild; 183 return {svgOuter, svgInnerHtml}; 184 } 185 186 export const SvgIcon = { 187 name: 'SvgIcon', 188 props: { 189 name: {type: String, required: true}, 190 size: {type: Number, default: 16}, 191 className: {type: String, default: ''}, 192 symbolId: {type: String}, 193 }, 194 render() { 195 let {svgOuter, svgInnerHtml} = svgParseOuterInner(this.name); 196 // https://vuejs.org/guide/extras/render-function.html#creating-vnodes 197 // the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc 198 const attrs = {}; 199 for (const attr of svgOuter.attributes) { 200 if (attr.name === 'class') continue; 201 attrs[`^${attr.name}`] = attr.value; 202 } 203 attrs[`^width`] = this.size; 204 attrs[`^height`] = this.size; 205 206 // make the <SvgIcon class="foo" class-name="bar"> classes work together 207 const classes = []; 208 for (const cls of svgOuter.classList) { 209 classes.push(cls); 210 } 211 // TODO: drop the `className/class-name` prop in the future, only use "class" prop 212 if (this.className) { 213 classes.push(...this.className.split(/\s+/).filter(Boolean)); 214 } 215 if (this.symbolId) { 216 classes.push('tw-hidden', 'svg-symbol-container'); 217 svgInnerHtml = `<symbol id="${this.symbolId}" viewBox="${attrs['^viewBox']}">${svgInnerHtml}</symbol>`; 218 } 219 // create VNode 220 return h('svg', { 221 ...attrs, 222 class: classes, 223 innerHTML: svgInnerHtml, 224 }); 225 }, 226 };