Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
koehr | e33adfe230 | 5 years ago |
koehr | 591530246b | 5 years ago |
@ -1 +0,0 @@
|
|||||||
/* /index.html 200
|
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.2 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><circle r="7" cx="7" cy="7" stroke="black" stroke-width="2" fill="none" /></svg>
|
|
Before Width: | Height: | Size: 164 B |
@ -1 +0,0 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><rect width="14" height="14" stroke="black" stroke-width="2" fill="none" /></svg>
|
|
Before Width: | Height: | Size: 165 B |
@ -1 +0,0 @@
|
|||||||
<svg width="19" height="4" viewBox="0 0 19 4" xmlns="http://www.w3.org/2000/svg"><path d="M1.25 0H7a1.25 1.25 0 1 1 0 2.5H1.25a1.25 1.25 0 1 1 0-2.5zM11 0h5.75a1.25 1.25 0 0 1 0 2.5H11A1.25 1.25 0 0 1 11 0z"/></svg>
|
|
Before Width: | Height: | Size: 216 B |
@ -1 +0,0 @@
|
|||||||
<svg width="19" height="4" viewBox="0 0 19 4" xmlns="http://www.w3.org/2000/svg"><path d="m 16.61577,0 -5.820501,0.28200371 c -1.6647133,0.0806555 -1.6633064,1.74209559 0,1.84786639 L 16.61577,2.5 c 1.663306,0.1057709 1.664713,-2.58065548 0,-2.5 z M 6.9186458,0.51113173 1.2612438,0.8454868 c -1.66376299,0.0983292 -1.68672099,0.7081403 -0.0221,0.7906147 l 5.697126,0.2822659 c 1.664625,0.082474 1.6461385,-1.50556481 -0.017624,-1.40723567 z" /></svg>
|
|
Before Width: | Height: | Size: 452 B |
@ -1 +0,0 @@
|
|||||||
<svg width="19" height="4" viewBox="0 0 19 4" xmlns="http://www.w3.org/2000/svg"><path d="m 1.25,0 5.8205009,0.28200371 c 1.6647132,0.0806555 1.6633063,1.74209559 0,1.84786639 L 1.25,2.5 c -1.66330631,0.1057709 -1.66471321,-2.58065548 0,-2.5 z m 9.697124,0.51113173 5.657402,0.33435507 c 1.663763,0.0983292 1.686721,0.7081403 0.0221,0.7906147 L 10.9295,1.9183674 C 9.264875,2.0008418 9.2833615,0.41280259 10.947124,0.51113173 Z" /></svg>
|
|
Before Width: | Height: | Size: 438 B |
@ -1 +0,0 @@
|
|||||||
<svg width="10" height="14" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="M7.6 8.15H2.25v4.525a1.125 1.125 0 0 1-2.25 0V1.125a1.125 1.125 0 1 1 2.25 0V5.9H7.6V1.125a1.125 1.125 0 0 1 2.25 0v11.55a1.125 1.125 0 0 1-2.25 0V8.15z"/></svg>
|
|
Before Width: | Height: | Size: 254 B |
@ -1 +0,0 @@
|
|||||||
<svg width="16" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.14 1.494V4.98h4.62V1.494c0-.498.098-.871.293-1.12A.927.927 0 0 1 7.82 0c.322 0 .583.123.782.37.2.246.3.62.3 1.124v9.588c0 .503-.101.88-.303 1.128a.957.957 0 0 1-.779.374.921.921 0 0 1-.77-.378c-.193-.251-.29-.626-.29-1.124V6.989H2.14v4.093c0 .503-.1.88-.302 1.128a.957.957 0 0 1-.778.374.921.921 0 0 1-.772-.378C.096 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.285.374A.922.922 0 0 1 1.06 0c.321 0 .582.123.782.37.199.246.299.62.299 1.124zm11.653 9.985V5.27c-1.279.887-2.14 1.33-2.583 1.33a.802.802 0 0 1-.563-.228.703.703 0 0 1-.245-.529c0-.232.08-.402.241-.511.161-.11.446-.25.854-.424.61-.259 1.096-.532 1.462-.818a5.84 5.84 0 0 0 .97-.962c.282-.355.466-.573.552-.655.085-.082.246-.123.483-.123.267 0 .481.093.642.28.161.186.242.443.242.77v7.813c0 .914-.345 1.371-1.035 1.371-.307 0-.554-.093-.74-.28-.187-.186-.28-.461-.28-.825z"/></svg>
|
|
Before Width: | Height: | Size: 918 B |
@ -1 +0,0 @@
|
|||||||
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm10.99 9.288h3.527c.351 0 .62.072.804.216.185.144.277.34.277.588 0 .22-.073.408-.22.56-.146.154-.368.23-.665.23h-4.972c-.338 0-.601-.093-.79-.28a.896.896 0 0 1-.284-.659c0-.162.06-.377.182-.645s.255-.478.399-.631a38.617 38.617 0 0 1 1.621-1.598c.482-.444.827-.735 1.034-.875.369-.261.676-.523.922-.787.245-.263.432-.534.56-.81.129-.278.193-.549.193-.815 0-.288-.069-.546-.206-.773a1.428 1.428 0 0 0-.56-.53 1.618 1.618 0 0 0-.774-.19c-.59 0-1.054.26-1.392.777-.045.068-.12.252-.226.554-.106.302-.225.534-.358.696-.133.162-.328.243-.585.243a.76.76 0 0 1-.56-.223c-.149-.148-.223-.351-.223-.608 0-.31.07-.635.21-.972.139-.338.347-.645.624-.92a3.093 3.093 0 0 1 1.054-.665c.426-.169.924-.253 1.496-.253.69 0 1.277.108 1.764.324.315.144.592.343.83.595.24.252.425.544.558.875.133.33.2.674.2 1.03 0 .558-.14 1.066-.416 1.523-.277.457-.56.815-.848 1.074-.288.26-.771.666-1.45 1.22-.677.554-1.142.984-1.394 1.29a3.836 3.836 0 0 0-.331.44z"/></svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm11.61 4.919c.418 0 .778-.123 1.08-.368.301-.245.452-.597.452-1.055 0-.35-.12-.65-.36-.902-.241-.252-.566-.378-.974-.378-.277 0-.505.038-.684.116a1.1 1.1 0 0 0-.426.306 2.31 2.31 0 0 0-.296.49c-.093.2-.178.388-.255.565a.479.479 0 0 1-.245.225.965.965 0 0 1-.409.081.706.706 0 0 1-.5-.22c-.152-.148-.228-.345-.228-.59 0-.236.071-.484.214-.745a2.72 2.72 0 0 1 .627-.746 3.149 3.149 0 0 1 1.024-.568 4.122 4.122 0 0 1 1.368-.214c.44 0 .842.06 1.205.18.364.12.679.294.947.52.267.228.47.49.606.79.136.3.204.622.204.967 0 .454-.099.843-.296 1.168-.198.324-.48.64-.848.95.354.19.653.408.895.653.243.245.426.516.548.813.123.298.184.619.184.964 0 .413-.083.812-.248 1.198-.166.386-.41.73-.732 1.031a3.49 3.49 0 0 1-1.147.708c-.443.17-.932.256-1.467.256a3.512 3.512 0 0 1-1.464-.293 3.332 3.332 0 0 1-1.699-1.64c-.142-.314-.214-.573-.214-.777 0-.263.085-.475.255-.636a.89.89 0 0 1 .637-.242c.127 0 .25.037.367.112a.53.53 0 0 1 .232.27c.236.63.489 1.099.759 1.405.27.306.65.46 1.14.46a1.714 1.714 0 0 0 1.46-.824c.17-.273.256-.588.256-.947 0-.53-.145-.947-.436-1.249-.29-.302-.694-.453-1.212-.453-.09 0-.231.01-.422.028-.19.018-.313.027-.367.027-.25 0-.443-.062-.579-.187-.136-.125-.204-.299-.204-.521 0-.218.081-.394.245-.528.163-.134.406-.2.728-.2h.28z"/></svg>
|
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="20" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm13.003 10.09v-1.252h-3.38c-.427 0-.746-.097-.96-.29-.213-.193-.32-.456-.32-.788 0-.085.016-.171.048-.259.031-.088.078-.18.141-.276.063-.097.128-.19.195-.28.068-.09.15-.2.25-.33l3.568-4.774a5.44 5.44 0 0 1 .576-.683.763.763 0 0 1 .542-.212c.682 0 1.023.39 1.023 1.171v5.212h.29c.346 0 .623.047.832.142.208.094.313.3.313.62 0 .26-.086.45-.256.568-.17.12-.427.179-.768.179h-.41v1.252c0 .346-.077.603-.23.771-.152.168-.356.253-.612.253a.78.78 0 0 1-.61-.26c-.154-.173-.232-.427-.232-.764zm-2.895-2.76h2.895V4.91L12.26 8.823z"/></svg>
|
|
Before Width: | Height: | Size: 1.0 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm14.16 2.645h-3.234l-.388 2.205c.644-.344 1.239-.517 1.783-.517.436 0 .843.082 1.222.245.38.164.712.39.998.677.286.289.51.63.674 1.025.163.395.245.82.245 1.273 0 .658-.148 1.257-.443 1.797-.295.54-.72.97-1.276 1.287-.556.318-1.197.477-1.923.477-.813 0-1.472-.15-1.978-.45-.506-.3-.865-.643-1.076-1.031-.21-.388-.316-.727-.316-1.018 0-.177.073-.345.22-.504a.725.725 0 0 1 .556-.238c.381 0 .665.22.85.66.182.404.427.719.736.943.309.225.654.337 1.035.337.35 0 .656-.09.919-.272.263-.182.466-.431.61-.749.142-.318.214-.678.214-1.082 0-.436-.078-.808-.232-1.117a1.607 1.607 0 0 0-.62-.69 1.674 1.674 0 0 0-.864-.229c-.39 0-.67.048-.837.143-.168.095-.41.262-.725.5-.316.239-.576.358-.78.358a.843.843 0 0 1-.592-.242c-.173-.16-.259-.344-.259-.548 0-.022.025-.177.075-.463l.572-3.26c.063-.39.181-.675.354-.852.172-.177.454-.265.844-.265h3.595c.708 0 1.062.27 1.062.81a.711.711 0 0 1-.26.572c-.172.145-.426.218-.762.218z"/></svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zM12.53 7.058a3.093 3.093 0 0 1 1.004-.814 2.734 2.734 0 0 1 1.214-.264c.43 0 .827.08 1.19.24.365.161.684.39.957.686.274.296.485.645.635 1.048a3.6 3.6 0 0 1 .223 1.262c0 .637-.145 1.216-.437 1.736-.292.52-.699.926-1.221 1.218-.522.292-1.114.438-1.774.438-.76 0-1.416-.186-1.967-.557-.552-.37-.974-.919-1.265-1.645-.292-.726-.438-1.613-.438-2.662 0-.855.088-1.62.265-2.293.176-.674.43-1.233.76-1.676.33-.443.73-.778 1.2-1.004.47-.226 1.006-.339 1.608-.339.579 0 1.089.113 1.53.34.44.225.773.506.997.84.224.335.335.656.335.964 0 .185-.07.354-.21.505a.698.698 0 0 1-.536.227.874.874 0 0 1-.529-.18 1.039 1.039 0 0 1-.36-.498 1.42 1.42 0 0 0-.495-.655 1.3 1.3 0 0 0-.786-.247c-.24 0-.479.069-.716.207a1.863 1.863 0 0 0-.6.56c-.33.479-.525 1.333-.584 2.563zm1.832 4.213c.456 0 .834-.186 1.133-.56.298-.373.447-.862.447-1.468 0-.412-.07-.766-.21-1.062a1.584 1.584 0 0 0-.577-.678 1.47 1.47 0 0 0-.807-.234c-.28 0-.548.074-.804.224-.255.149-.461.365-.617.647a2.024 2.024 0 0 0-.234.994c0 .61.158 1.12.475 1.527.316.407.714.61 1.194.61z"/></svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="17" height="13" viewBox="0 0 17 13" xmlns="http://www.w3.org/2000/svg"> <path d="M5.625 4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0-4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0 9.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm-4.5-5a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0-4.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0 9.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25z"/></svg>
|
|
Before Width: | Height: | Size: 488 B |
@ -1 +0,0 @@
|
|||||||
<svg width="17" height="13" viewBox="0 0 17 13" xmlns="http://www.w3.org/2000/svg"><path d="M5.819 4.607h9.362a1.069 1.069 0 0 1 0 2.138H5.82a1.069 1.069 0 1 1 0-2.138zm0-4.607h9.362a1.069 1.069 0 0 1 0 2.138H5.82a1.069 1.069 0 1 1 0-2.138zm0 9.357h9.362a1.069 1.069 0 0 1 0 2.138H5.82a1.069 1.069 0 0 1 0-2.137zM1.468 4.155V1.33c-.554.404-.926.606-1.118.606a.338.338 0 0 1-.244-.104A.327.327 0 0 1 0 1.59c0-.107.035-.184.105-.234.07-.05.192-.114.369-.192.264-.118.475-.243.633-.373.158-.13.298-.276.42-.438a3.94 3.94 0 0 1 .238-.298C1.802.019 1.872 0 1.975 0c.115 0 .208.042.277.127.07.085.105.202.105.351v3.556c0 .416-.15.624-.448.624a.421.421 0 0 1-.32-.127c-.08-.085-.121-.21-.121-.376zm-.283 6.664h1.572c.156 0 .275.03.358.091a.294.294 0 0 1 .123.25.323.323 0 0 1-.098.238c-.065.065-.164.097-.296.097H.629a.494.494 0 0 1-.353-.119.372.372 0 0 1-.126-.28c0-.068.027-.16.081-.273a.977.977 0 0 1 .178-.268c.267-.264.507-.49.722-.678.215-.188.368-.312.46-.371.165-.11.302-.222.412-.334.109-.112.192-.226.25-.344a.786.786 0 0 0 .085-.345.6.6 0 0 0-.341-.553.75.75 0 0 0-.345-.08c-.263 0-.47.11-.62.329-.02.029-.054.107-.101.235a.966.966 0 0 1-.16.295c-.059.069-.145.103-.26.103a.348.348 0 0 1-.25-.094.34.34 0 0 1-.099-.258c0-.132.031-.27.093-.413.063-.143.155-.273.279-.39.123-.116.28-.21.47-.282.189-.072.411-.107.666-.107.307 0 .569.045.786.137a1.182 1.182 0 0 1 .618.623 1.18 1.18 0 0 1-.096 1.083 2.03 2.03 0 0 1-.378.457c-.128.11-.344.282-.646.517-.302.235-.509.417-.621.547a1.637 1.637 0 0 0-.148.187z"/></svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="17" height="13" viewBox="0 0 17 13" xmlns="http://www.w3.org/2000/svg"> <path d="M5.625 4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0-4.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm0 9.85h9.25a1.125 1.125 0 0 1 0 2.25h-9.25a1.125 1.125 0 0 1 0-2.25zm-4.5-5a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0-4.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25zm0 9.85a1.125 1.125 0 1 1 0 2.25 1.125 1.125 0 0 1 0-2.25z"/></svg>
|
|
Before Width: | Height: | Size: 488 B |
@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0.2 -0.3 9 11.4" width="12" height="14">
|
|
||||||
<path d="M0 2.77V.92A1 1 0 01.2.28C.35.1.56 0 .83 0h7.66c.28.01.48.1.63.28.14.17.21.38.21.64v1.85c0 .26-.08.48-.23.66-.15.17-.37.26-.66.26-.28 0-.5-.09-.64-.26a1 1 0 01-.21-.66V1.69H5.6v7.58h.5c.25 0 .45.08.6.23.17.16.25.35.25.6s-.08.45-.24.6a.87.87 0 01-.62.22H3.21a.87.87 0 01-.61-.22.78.78 0 01-.24-.6c0-.25.08-.44.24-.6a.85.85 0 01.61-.23h.5V1.7H1.73v1.08c0 .26-.08.48-.23.66-.15.17-.37.26-.66.26-.28 0-.5-.09-.64-.26A1 1 0 010 2.77z"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 540 B |
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<ol>
|
||||||
|
<li :key="n" v-for="n in amount" :style="style"> </li>
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardBoxes extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
|
||||||
|
private get amount () {
|
||||||
|
return parseInt(this.params[0], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get style () {
|
||||||
|
const size = parseFloat(this.params[1])
|
||||||
|
return {
|
||||||
|
width: `calc(${size}em - 0.25em)`,
|
||||||
|
height: `calc(${size}em - 0.25em)`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
ol {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
ol > li {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0 .3em .3em 0;
|
||||||
|
border: 0.25em solid var(--highlight-color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,51 @@
|
|||||||
|
<template>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(param, i) in params"
|
||||||
|
:key="`param${i}`"
|
||||||
|
v-editable:[i]="editable"
|
||||||
|
@keydown="handleKey(i, $event)">
|
||||||
|
{{ param }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardBulletList extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
|
||||||
|
private addEntry (index: number) {
|
||||||
|
const newParams = [...this.params]
|
||||||
|
newParams.splice(index + 1, 0, '')
|
||||||
|
this.$emit('replace', newParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeEntry (index: number) {
|
||||||
|
const newParams = [...this.params]
|
||||||
|
newParams.splice(index, 1)
|
||||||
|
this.$emit('replace', newParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKey (index: number, event: KeyboardEvent) {
|
||||||
|
const { key, shiftKey } = event
|
||||||
|
if (key === 'Enter' && shiftKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
this.addEntry(index)
|
||||||
|
} else if (key === 'Backspace') {
|
||||||
|
const text = (event.target as HTMLElement).innerText
|
||||||
|
if (text.trim() === '') this.removeEntry(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
ul {
|
||||||
|
list-style-position: inside;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: .5em;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<p>
|
||||||
|
<span v-editable:0="editable" class="title">{{ params[0] }}</span>
|
||||||
|
<span v-editable:1="editable">{{ params[1] }}</span>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardDescription extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p { margin: 0; line-height: 1.2; }
|
||||||
|
p > .title { font-weight: bold; font-style: italic; }
|
||||||
|
p > .title::after { content: ' '; }
|
||||||
|
</style>
|
@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<ol>
|
||||||
|
<li :key="titles[i]" v-for="(v, i) in params">
|
||||||
|
<span class="title">{{ titles[i] }}</span>
|
||||||
|
<span class="description">{{ v }} ({{ mod(v) }})</span>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardDndstats extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
|
||||||
|
private titles = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']
|
||||||
|
|
||||||
|
private mod (v: number): string {
|
||||||
|
switch (v) {
|
||||||
|
case 1: return '-5'
|
||||||
|
case 2:
|
||||||
|
case 3: return '-4'
|
||||||
|
case 4:
|
||||||
|
case 5: return '-3'
|
||||||
|
case 6:
|
||||||
|
case 7: return '-2'
|
||||||
|
case 8:
|
||||||
|
case 9: return '-1'
|
||||||
|
case 10:
|
||||||
|
case 11: return '0'
|
||||||
|
case 12:
|
||||||
|
case 13: return '+1'
|
||||||
|
case 14:
|
||||||
|
case 15: return '+2'
|
||||||
|
case 16:
|
||||||
|
case 17: return '+3'
|
||||||
|
case 18:
|
||||||
|
case 19: return '+4'
|
||||||
|
case 20:
|
||||||
|
case 21: return '+5'
|
||||||
|
case 22:
|
||||||
|
case 23: return '+6'
|
||||||
|
case 24:
|
||||||
|
case 25: return '+7'
|
||||||
|
case 26:
|
||||||
|
case 27: return '+8'
|
||||||
|
case 28:
|
||||||
|
case 29: return '+9'
|
||||||
|
default: return '+10'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
ol {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
ol > li {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.title { font-weight: bold; }
|
||||||
|
</style>
|
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive, focused }">
|
||||||
|
<div class="menu-bar" :class="{ active: focused }">
|
||||||
|
<button class="editor-button-bold" :class="{ active: active.bold }" @click="menuAction('bold', commands.bold, isActive)" />
|
||||||
|
<button class="editor-button-italic" :class="{ active: active.italic }" @click="menuAction('italic', commands.italic, isActive)" />
|
||||||
|
|
||||||
|
<button class="editor-button-paragraph" :class="{ active: active.paragraph }" @click="menuAction('paragraph', commands.paragraph, isActive)" />
|
||||||
|
<button class="editor-button-heading2" :class="{ active: active.heading2 }" @click="menuAction('heading2', commands.heading({ level: 2}), isActive)" />
|
||||||
|
<button class="editor-button-heading3" :class="{ active: active.heading3 }" @click="menuAction('heading3', commands.heading({ level: 3}), isActive)" />
|
||||||
|
|
||||||
|
<button class="editor-button-bullet-list" :class="{ active: active.bullet_list }" @click="menuAction('bullet_list', commands.bullet_list, isActive)" />
|
||||||
|
<button class="editor-button-horizontal-rule" :class="{ active: active.horizontal_rule }" @click="menuAction('horizontal_rule', commands.horizontal_rule, isActive)" />
|
||||||
|
|
||||||
|
<button class="editor-button-stat-block" :class="{ active: active.stat_block }" @click="menuAction('stat_block', commands.stat_block, isActive)" />
|
||||||
|
</div>
|
||||||
|
</editor-menu-bar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
import { Editor, EditorMenuBar } from 'tiptap'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: { EditorMenuBar }
|
||||||
|
})
|
||||||
|
export default class DeckCardEditorMenu extends Vue {
|
||||||
|
@Prop() public readonly editor!: Editor
|
||||||
|
|
||||||
|
private active: {[key: string]: boolean} = {
|
||||||
|
bold: false,
|
||||||
|
italic: false,
|
||||||
|
|
||||||
|
paragraph: false,
|
||||||
|
heading2: false,
|
||||||
|
heading3: false,
|
||||||
|
|
||||||
|
bulletList: false,
|
||||||
|
horizontalLule: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private menuAction (name: string, command: () => void, isActive: {[key: string]: () => boolean}) {
|
||||||
|
command()
|
||||||
|
|
||||||
|
Object.keys(this.active).forEach(action => {
|
||||||
|
this.active[action] = isActive[action]()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.card-front > main > .menu-bar {
|
||||||
|
position: absolute;
|
||||||
|
width: 70%;
|
||||||
|
margin: -3rem 0 0 -1rem;
|
||||||
|
padding: .2rem 1rem;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s, visibility 0.2s;
|
||||||
|
background-color: var(--highlight-color);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.card-front > main > .menu-bar.active {
|
||||||
|
opacity: 1.0;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
.menu-bar > button {
|
||||||
|
position: relative;
|
||||||
|
width: 1.6rem;
|
||||||
|
height: 1.6rem;
|
||||||
|
margin: 0 .1rem;
|
||||||
|
background-color: #EEE;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 75%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
.menu-bar > button:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 1.6rem;
|
||||||
|
width: 1.6rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.menu-bar > button.active {
|
||||||
|
background-color: #FF0;
|
||||||
|
}
|
||||||
|
.editor-button-bold { background-image: url(../assets/zondicons/format-bold.svg); }
|
||||||
|
.editor-button-italic { background-image: url(../assets/zondicons/format-italic.svg); }
|
||||||
|
.editor-button-bullet-list { background-image: url(../assets/zondicons/list-bullet.svg); }
|
||||||
|
|
||||||
|
.editor-button-heading2:after { content: 'H2'; }
|
||||||
|
.editor-button-heading3:after { content: 'H3'; }
|
||||||
|
.editor-button-paragraph:after { content: 'P'; }
|
||||||
|
.editor-button-horizontal-rule:after { content: '—'; }
|
||||||
|
|
||||||
|
.editor-button-stat-block:after { content: 'ST'; }
|
||||||
|
</style>
|
@ -1,163 +0,0 @@
|
|||||||
<template>
|
|
||||||
<main ref="cardEl" class="card-content"></main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator'
|
|
||||||
|
|
||||||
import Editor from '@editorjs/editorjs'
|
|
||||||
import List from '@editorjs/list'
|
|
||||||
import { Heading, Delimiter, Charges, DnDStats } from '@/editor'
|
|
||||||
|
|
||||||
@Component
|
|
||||||
export default class DeckCardEditor extends Vue {
|
|
||||||
@Prop() public readonly cardId!: string
|
|
||||||
@Prop() public readonly active!: boolean
|
|
||||||
@Prop() public readonly content!: Card['content']
|
|
||||||
|
|
||||||
private editor!: Editor
|
|
||||||
|
|
||||||
private get id () {
|
|
||||||
return `${this.cardId}-editor`
|
|
||||||
}
|
|
||||||
|
|
||||||
private mounted () {
|
|
||||||
this.editor = new Editor({
|
|
||||||
holder: this.$refs.cardEl as HTMLElement,
|
|
||||||
autofocus: false,
|
|
||||||
tools: {
|
|
||||||
// header: Heading,
|
|
||||||
list: { class: List, inlineToolbar: true },
|
|
||||||
heading: { class: Heading, inlineToolbar: true },
|
|
||||||
delimiter: { class: Delimiter, inlineToolbar: false },
|
|
||||||
charges: { class: Charges, inlineToolbar: false },
|
|
||||||
dndstats: { class: DnDStats, inlineToolbar: false }
|
|
||||||
},
|
|
||||||
data: this.content,
|
|
||||||
placeholder: 'Click here to write your card.',
|
|
||||||
onChange: () => {
|
|
||||||
console.log('editor change, saving')
|
|
||||||
this.editor.save().then(value => {
|
|
||||||
this.$emit('change', { field: 'content', value })
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('error saving data', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.card-content .cdx-block {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content .ce-paragraph, .card-content p {
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content ul {
|
|
||||||
list-style-position: inside;
|
|
||||||
margin: 0;
|
|
||||||
padding-left: .5em;
|
|
||||||
}
|
|
||||||
.card-content li > p {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content h2 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: var(--highlight-color);
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content h3 {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: var(--highlight-color);
|
|
||||||
margin: 0 0 .2em 0;
|
|
||||||
font-weight: normal;
|
|
||||||
font-variant: small-caps;
|
|
||||||
line-height: .9em;
|
|
||||||
border-bottom: 1px solid var(--highlight-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content .card-delimiter {
|
|
||||||
height: 0;
|
|
||||||
margin: .2em 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 2px solid var(--highlight-color);
|
|
||||||
}
|
|
||||||
.card-content .card-delimiter.pointing-right {
|
|
||||||
height: 0;
|
|
||||||
margin: .2em 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 2px 0 2px 220px;
|
|
||||||
border-color: transparent transparent transparent var(--highlight-color);
|
|
||||||
}
|
|
||||||
.card-content .card-delimiter.pointing-left {
|
|
||||||
height: 0;
|
|
||||||
margin: .2em 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 2px 220px 2px 0;
|
|
||||||
border-color: transparent var(--highlight-color) transparent transparent;
|
|
||||||
}
|
|
||||||
.card-content .cdx-list__item {
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
.card-content .card-charges-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 1em;
|
|
||||||
}
|
|
||||||
.card-content .card-charges-wrapper.card-charges-stretch { justify-content: space-around; }
|
|
||||||
.card-content .card-charges-wrapper > .card-charge {
|
|
||||||
width: 1.0em;
|
|
||||||
height: 1.0em;
|
|
||||||
border: 2px solid var(--highlight-color);
|
|
||||||
margin: .5em .2em;
|
|
||||||
}
|
|
||||||
.card-content .card-charges-wrapper > .card-charge-circle { border-radius: 100%; }
|
|
||||||
.card-content .card-charges-wrapper > .card-charge-size-1 { width: 1.0em; height: 1.0em; }
|
|
||||||
.card-content .card-charges-wrapper > .card-charge-size-2 { width: 1.2em; height: 1.2em; }
|
|
||||||
.card-content .card-charges-wrapper > .card-charge-size-3 { width: 1.4em; height: 1.4em; }
|
|
||||||
.card-content .card-charges-wrapper > .card-charge-size-4 { width: 1.6em; height: 1.6em; }
|
|
||||||
.card-content .card-charges-wrapper > .card-charge-size-5 { width: 1.8em; height: 1.8em; }
|
|
||||||
|
|
||||||
.card-content .card-dnd-stats {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--highlight-color);
|
|
||||||
}
|
|
||||||
.card-content .dnd-stat-block {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
font-size: .8em;
|
|
||||||
}
|
|
||||||
.card-content .dnd-stat-block > .dnd-stat-title {
|
|
||||||
width: 100%;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.card-content .dnd-stat-block > input {
|
|
||||||
width: 50%;
|
|
||||||
background: white;
|
|
||||||
color: var(--highlight-color);
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.card-content .dnd-stat-block {
|
|
||||||
}
|
|
||||||
|
|
||||||
[contenteditable="true"] { outline: none; }
|
|
||||||
</style>
|
|
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="{flex: params[0]}"> </div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardFill extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<p v-editable:0="editable">{{ params[0] }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardNote extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p { margin: 0 0 .5em 0; line-height: 1.2; font-style: italic; }
|
||||||
|
</style>
|
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<p>
|
||||||
|
<span class="title" v-editable:0="editable">{{ params[0] }}</span>
|
||||||
|
<span class="description" v-editable:1="editable">{{ params[1] }}</span>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardProperty extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p { margin: 0 0 0 1em; line-height: 1.2; text-indent: -1em; }
|
||||||
|
p > .title { font-weight: bold; }
|
||||||
|
p > .title::after { content: ' '; }
|
||||||
|
</style>
|
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<hr :class="params[0]" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardRule extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
hr {
|
||||||
|
height: 0;
|
||||||
|
margin: .2em 0;
|
||||||
|
border: 2px solid var(--highlight-color);
|
||||||
|
}
|
||||||
|
hr.pointing-right {
|
||||||
|
height: 0;
|
||||||
|
margin: .2em 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px 0 2px 220px;
|
||||||
|
border-color: transparent transparent transparent var(--highlight-color);
|
||||||
|
}
|
||||||
|
hr.pointing-left {
|
||||||
|
height: 0;
|
||||||
|
margin: .2em 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 2px 220px 2px 0;
|
||||||
|
border-color: transparent var(--highlight-color) transparent transparent;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<h4 v-editable:0="editable">{{ params[0] }}</h4>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardSection extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h4 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--highlight-color);
|
||||||
|
margin: 0 0 .2em 0;
|
||||||
|
font-weight: normal;
|
||||||
|
font-variant: small-caps;
|
||||||
|
line-height: .9em;
|
||||||
|
border-bottom: 1px solid var(--highlight-color);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<h3 v-editable:0="editable">{{ params[0] }}</h3>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardSubtitle extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h3 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--highlight-color);
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<p v-editable:0="editable">{{ params[0] }}</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DeckCardText extends Vue {
|
||||||
|
@Prop() public readonly params!: string[]
|
||||||
|
@Prop() public readonly editable!: boolean
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p { margin: 0 0 .5em 0; line-height: 1.2; }
|
||||||
|
</style>
|
@ -1,137 +0,0 @@
|
|||||||
import { ContentlessBlock, BlockToolArgs } from './contentless-block'
|
|
||||||
import icon from '../assets/editor/charges.svg.txt'
|
|
||||||
import iconCircle from '../assets/editor/charges-circle.svg.txt'
|
|
||||||
|
|
||||||
const title = 'Charges'
|
|
||||||
|
|
||||||
interface ChargesData {
|
|
||||||
variant: string;
|
|
||||||
amount: number;
|
|
||||||
size: number;
|
|
||||||
stretch: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Charges extends ContentlessBlock {
|
|
||||||
static MIN_SIZE = 1
|
|
||||||
static MAX_SIZE = 5
|
|
||||||
private _variant: string
|
|
||||||
private _amount: number
|
|
||||||
private _size: number
|
|
||||||
private _stretch: boolean
|
|
||||||
|
|
||||||
constructor (args: BlockToolArgs) {
|
|
||||||
super(args)
|
|
||||||
console.log('new charges', args)
|
|
||||||
this._settingButtons = [
|
|
||||||
{ name: 'box', icon, action: (name: string) => this.setVariant(name) },
|
|
||||||
{ name: 'more', icon: icon, action: () => this.increaseAmount() },
|
|
||||||
{ name: 'bigger', icon: icon, action: () => this.increaseSize() },
|
|
||||||
{ name: 'circle', icon: iconCircle, action: (name: string) => this.setVariant(name) },
|
|
||||||
{ name: 'less', icon: icon, action: () => this.decreaseAmount() },
|
|
||||||
{ name: 'smaller', icon: icon, action: () => this.decreaseSize() },
|
|
||||||
{ name: 'toggle-stretch', icon: icon, action: () => this.toggleStretch() }
|
|
||||||
]
|
|
||||||
const { variant, amount, size, stretch } = (args.data || {}) as ChargesData
|
|
||||||
|
|
||||||
this._variant = variant || 'box'
|
|
||||||
this._amount = amount || 5
|
|
||||||
this._size = size || 1
|
|
||||||
this._stretch = !(stretch === false)
|
|
||||||
|
|
||||||
this._element = this._render()
|
|
||||||
}
|
|
||||||
|
|
||||||
private setVariant (variant: string) {
|
|
||||||
if (this._variant === variant) return
|
|
||||||
|
|
||||||
const charges = Array.from(this._element.children)
|
|
||||||
|
|
||||||
charges.forEach(charge => {
|
|
||||||
charge.classList.remove(`card-charge-${this._variant}`)
|
|
||||||
charge.classList.add(`card-charge-${variant}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this._variant = variant
|
|
||||||
}
|
|
||||||
|
|
||||||
private toggleStretch () {
|
|
||||||
if (this._stretch) this._element.classList.remove('card-charges-stretch')
|
|
||||||
else this._element.classList.add('card-charges-stretch')
|
|
||||||
this._stretch = !this._stretch
|
|
||||||
}
|
|
||||||
|
|
||||||
private createCharge (): HTMLElement {
|
|
||||||
const charge = document.createElement('DIV')
|
|
||||||
charge.classList.add('card-charge', `card-charge-${this._variant}`, `card-charge-size-${this._size}`)
|
|
||||||
return charge
|
|
||||||
}
|
|
||||||
|
|
||||||
private increaseAmount () {
|
|
||||||
this._element.appendChild(this.createCharge())
|
|
||||||
this._amount++
|
|
||||||
}
|
|
||||||
|
|
||||||
private decreaseAmount () {
|
|
||||||
const child = this._element.lastElementChild
|
|
||||||
if (child) {
|
|
||||||
this._element.removeChild(child)
|
|
||||||
this._amount--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private increaseSize () {
|
|
||||||
if (this._size >= Charges.MAX_SIZE) return
|
|
||||||
|
|
||||||
const charges = Array.from(this._element.children)
|
|
||||||
|
|
||||||
charges.forEach(charge => {
|
|
||||||
charge.classList.remove(`card-charge-size-${this._size}`)
|
|
||||||
charge.classList.add(`card-charge-size-${this._size + 1}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this._size++
|
|
||||||
}
|
|
||||||
|
|
||||||
private decreaseSize () {
|
|
||||||
if (this._size <= Charges.MIN_SIZE) return
|
|
||||||
|
|
||||||
const charges = Array.from(this._element.children)
|
|
||||||
|
|
||||||
charges.forEach(charge => {
|
|
||||||
charge.classList.remove(`card-charge-size-${this._size}`)
|
|
||||||
charge.classList.add(`card-charge-size-${this._size - 1}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
this._size--
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _render (): HTMLElement {
|
|
||||||
const el = document.createElement('DIV')
|
|
||||||
el.classList.add('card-charges-wrapper', this._CSS.block)
|
|
||||||
|
|
||||||
if (this._stretch) el.classList.add('card-charges-stretch')
|
|
||||||
|
|
||||||
for (let i = 0; i < this._amount; i++) {
|
|
||||||
el.appendChild(this.createCharge())
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('rendered', this._amount, 'charges', el)
|
|
||||||
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
public save (): ChargesData {
|
|
||||||
return {
|
|
||||||
variant: this._variant,
|
|
||||||
amount: this._amount,
|
|
||||||
size: this._size,
|
|
||||||
stretch: this._stretch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get toolbox () {
|
|
||||||
return { icon, title }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Charges
|
|
@ -1,222 +0,0 @@
|
|||||||
import {
|
|
||||||
BlockTool,
|
|
||||||
BlockToolData,
|
|
||||||
ToolboxConfig,
|
|
||||||
API,
|
|
||||||
HTMLPasteEvent,
|
|
||||||
ToolSettings,
|
|
||||||
SanitizerConfig
|
|
||||||
} from '@editorjs/editorjs'
|
|
||||||
|
|
||||||
export { HTMLPasteEvent } from '@editorjs/editorjs'
|
|
||||||
|
|
||||||
interface PasteConfig {
|
|
||||||
tags: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentBlockConfig extends ToolSettings {
|
|
||||||
placeholder?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentBlockSettingButton {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
action: (name: string, event?: MouseEvent) => void; // action triggered by button
|
|
||||||
isActive?: (name: string) => boolean; // determine if current button is active
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ContentBlockSettings = ContentBlockSettingButton[]
|
|
||||||
|
|
||||||
export interface ContentBlockArgs {
|
|
||||||
api: API;
|
|
||||||
config?: ContentBlockConfig;
|
|
||||||
data?: BlockToolData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CSSClasses {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentBlockData extends BlockToolData {
|
|
||||||
text?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type importFunction = (str: string) => ContentBlockData
|
|
||||||
type exportFunction = (data: ContentBlockData) => string
|
|
||||||
|
|
||||||
export interface ConversionConfig {
|
|
||||||
import: string | importFunction;
|
|
||||||
export: string | exportFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContentBlock implements BlockTool {
|
|
||||||
// Default placeholder for Paragraph Tool
|
|
||||||
static get DEFAULT_PLACEHOLDER (): string {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
static _supportedTags: string[] = []
|
|
||||||
|
|
||||||
static _toolboxConfig: ToolboxConfig = {
|
|
||||||
icon: '<svg></svg>',
|
|
||||||
title: 'UnnamedContentPlugin'
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _defaultPlaceholder (): string {
|
|
||||||
return ContentBlock.DEFAULT_PLACEHOLDER
|
|
||||||
}
|
|
||||||
|
|
||||||
protected api: API
|
|
||||||
protected _element: HTMLElement
|
|
||||||
protected _data: ContentBlockData
|
|
||||||
protected _config: ContentBlockConfig
|
|
||||||
protected _placeholder: string
|
|
||||||
protected _CSS: CSSClasses = {}
|
|
||||||
protected onKeyUp: (event: KeyboardEvent) => void
|
|
||||||
protected _settingButtons: ContentBlockSettings = []
|
|
||||||
|
|
||||||
constructor ({ data, config, api }: ContentBlockArgs) {
|
|
||||||
this.api = api
|
|
||||||
this._config = config as ContentBlockConfig
|
|
||||||
this._CSS.block = this.api.styles.block
|
|
||||||
|
|
||||||
this.onKeyUp = (event: KeyboardEvent) => this._onKeyUp(event)
|
|
||||||
|
|
||||||
// Placeholder it is first Block
|
|
||||||
this._placeholder = config?.placeholder ? config.placeholder : this._defaultPlaceholder()
|
|
||||||
this._data = data as ContentBlockData
|
|
||||||
this._element = this._render()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if text content is empty and set empty string to inner html.
|
|
||||||
// We need this because some browsers (e.g. Safari) insert <br> into empty contenteditanle elements
|
|
||||||
_onKeyUp (event: KeyboardEvent) {
|
|
||||||
if (event.code !== 'Backspace' && event.code !== 'Delete') return
|
|
||||||
|
|
||||||
if (this._element.textContent === '') {
|
|
||||||
this._element.innerHTML = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// render tool view
|
|
||||||
// whenever a redraw is needed the result is saved in this._element
|
|
||||||
protected _render (): HTMLElement {
|
|
||||||
const el = document.createElement('DIV')
|
|
||||||
el.classList.add(this._CSS.block)
|
|
||||||
el.dataset.placeholder = this._placeholder
|
|
||||||
el.addEventListener('keyup', this.onKeyUp)
|
|
||||||
el.innerHTML = this.data.text || ''
|
|
||||||
el.contentEditable = 'true'
|
|
||||||
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return Tool's view
|
|
||||||
public render (): HTMLElement {
|
|
||||||
return this._element
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method that specified how to merge two Text blocks.
|
|
||||||
// Called by Editor.js by backspace at the beginning of the Block
|
|
||||||
public merge (data: ContentBlockData) {
|
|
||||||
this.data = {
|
|
||||||
text: (this.data.text || '') + data.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Paragraph block data (by default checks for emptiness)
|
|
||||||
public validate (savedData: ContentBlockData): boolean {
|
|
||||||
if (!savedData.text) return false
|
|
||||||
return savedData.text.trim() !== ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract Tool's data from the view
|
|
||||||
public save (toolsContent: HTMLElement): ContentBlockData {
|
|
||||||
return {
|
|
||||||
text: toolsContent.innerHTML
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public get CSS (): CSSClasses {
|
|
||||||
return this._CSS
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable Conversion Toolbar. Paragraph can be converted to/from other tools
|
|
||||||
*/
|
|
||||||
static get conversionConfig (): ConversionConfig {
|
|
||||||
return {
|
|
||||||
export: 'text', // to convert Paragraph to other block, use 'text' property of saved data
|
|
||||||
import: 'text' // to covert other block's exported string to Paragraph, fill 'text' property of tool data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitizer rules
|
|
||||||
static get sanitize (): SanitizerConfig {
|
|
||||||
return {
|
|
||||||
text: { br: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get data (): ContentBlockData {
|
|
||||||
const text = this._element?.innerHTML
|
|
||||||
if (text !== undefined) this._data.text = text
|
|
||||||
if (this._data.text === undefined) this._data.text = ''
|
|
||||||
return this._data
|
|
||||||
}
|
|
||||||
|
|
||||||
set data (data: ContentBlockData) {
|
|
||||||
this._data = data || {}
|
|
||||||
this._element.innerHTML = this._data.text || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderSettings (): HTMLElement {
|
|
||||||
const wrapper = document.createElement('DIV')
|
|
||||||
|
|
||||||
this._settingButtons.forEach(tune => {
|
|
||||||
// make sure the settings button does something
|
|
||||||
if (!tune.icon || typeof tune.action !== 'function') return
|
|
||||||
|
|
||||||
const { name, icon, action, isActive } = tune
|
|
||||||
|
|
||||||
const btn = document.createElement('SPAN')
|
|
||||||
btn.classList.add(this.api.styles.settingsButton)
|
|
||||||
|
|
||||||
if (typeof isActive === 'function' && isActive(name)) {
|
|
||||||
btn.classList.add(this.api.styles.settingsButtonActive)
|
|
||||||
}
|
|
||||||
btn.innerHTML = icon
|
|
||||||
btn.addEventListener('click', event => action(name, event))
|
|
||||||
|
|
||||||
wrapper.appendChild(btn)
|
|
||||||
})
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by Editor.js paste handling API.
|
|
||||||
// Provides configuration to handle the tools tags.
|
|
||||||
static get pasteConfig (): PasteConfig {
|
|
||||||
return {
|
|
||||||
tags: this._supportedTags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// overwrite this if you need special handling of paste data
|
|
||||||
protected pasteHandler (element: HTMLElement): ContentBlockData {
|
|
||||||
return { text: element.innerText }
|
|
||||||
}
|
|
||||||
|
|
||||||
// On paste callback fired from Editor.
|
|
||||||
public onPaste (event: HTMLPasteEvent) {
|
|
||||||
const element = event.detail.data
|
|
||||||
this.data = this.pasteHandler(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Icon and title for displaying at the Toolbox
|
|
||||||
static get toolbox (): ToolboxConfig {
|
|
||||||
return this._toolboxConfig
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ContentBlock
|
|
@ -1,73 +0,0 @@
|
|||||||
import { BlockTool, BlockToolData, ToolSettings, ToolboxConfig, API } from '@editorjs/editorjs'
|
|
||||||
import { ContentBlockSettings, CSSClasses } from './content-block'
|
|
||||||
|
|
||||||
export interface BlockToolArgs {
|
|
||||||
api: API;
|
|
||||||
config?: ToolSettings;
|
|
||||||
data?: BlockToolData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContentlessBlock implements BlockTool {
|
|
||||||
static get contentless () {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
protected api: API
|
|
||||||
protected _element: HTMLElement
|
|
||||||
protected _data: object
|
|
||||||
protected _config: ToolSettings
|
|
||||||
protected _CSS: CSSClasses = {}
|
|
||||||
protected _settingButtons: ContentBlockSettings = []
|
|
||||||
|
|
||||||
constructor ({ data, config, api }: BlockToolArgs) {
|
|
||||||
this.api = api
|
|
||||||
this._config = config as ToolSettings
|
|
||||||
this._data = data || {}
|
|
||||||
this._CSS.block = this.api.styles.block
|
|
||||||
this._element = this._render()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _render (): HTMLElement {
|
|
||||||
const el = document.createElement('DIV')
|
|
||||||
el.classList.add(this._CSS.block)
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
public render (): HTMLElement {
|
|
||||||
return this._element
|
|
||||||
}
|
|
||||||
|
|
||||||
public save (_toolsContent: HTMLElement): object {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderSettings (): HTMLElement {
|
|
||||||
const wrapper = document.createElement('DIV')
|
|
||||||
|
|
||||||
this._settingButtons.forEach(tune => {
|
|
||||||
// make sure the settings button does something
|
|
||||||
if (!tune.icon || typeof tune.action !== 'function') return
|
|
||||||
|
|
||||||
const { name, icon, action, isActive } = tune
|
|
||||||
|
|
||||||
const btn = document.createElement('SPAN')
|
|
||||||
btn.classList.add(this.api.styles.settingsButton)
|
|
||||||
|
|
||||||
if (typeof isActive === 'function' && isActive(name)) {
|
|
||||||
btn.classList.add(this.api.styles.settingsButtonActive)
|
|
||||||
}
|
|
||||||
btn.innerHTML = icon
|
|
||||||
btn.addEventListener('click', event => action(name, event))
|
|
||||||
|
|
||||||
wrapper.appendChild(btn)
|
|
||||||
})
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
static get toolbox (): ToolboxConfig {
|
|
||||||
return { icon: '<svg></svg>', title: 'UnnamedPlugin' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ContentlessBlock
|
|
@ -1,53 +0,0 @@
|
|||||||
import { ContentlessBlock, BlockToolArgs } from './contentless-block'
|
|
||||||
import icon from '../assets/editor/delimiter.svg.txt'
|
|
||||||
import iconR from '../assets/editor/delimiter_r.svg.txt'
|
|
||||||
import iconL from '../assets/editor/delimiter_l.svg.txt'
|
|
||||||
const title = 'Delimiter'
|
|
||||||
|
|
||||||
interface DelimiterData {
|
|
||||||
variant: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Delimiter extends ContentlessBlock {
|
|
||||||
private _variant = 'none'
|
|
||||||
|
|
||||||
constructor (args: BlockToolArgs) {
|
|
||||||
super(args)
|
|
||||||
this._settingButtons = [
|
|
||||||
{ name: 'straight', icon, action: (name: string) => this.setDelimiterType(name) },
|
|
||||||
{ name: 'pointing-left', icon: iconL, action: (name: string) => this.setDelimiterType(name) },
|
|
||||||
{ name: 'pointing-right', icon: iconR, action: (name: string) => this.setDelimiterType(name) }
|
|
||||||
]
|
|
||||||
const { variant } = (args.data || {}) as DelimiterData
|
|
||||||
if (variant) this.setDelimiterType(variant)
|
|
||||||
}
|
|
||||||
|
|
||||||
private setDelimiterType (name: string) {
|
|
||||||
this._element.classList.remove('pointing-left')
|
|
||||||
this._element.classList.remove('pointing-right')
|
|
||||||
this._variant = 'none'
|
|
||||||
|
|
||||||
if (name === 'pointing-left' || name === 'pointing-right') {
|
|
||||||
this._variant = name
|
|
||||||
this._element.classList.add(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _render (): HTMLElement {
|
|
||||||
const el = document.createElement('HR')
|
|
||||||
el.classList.add('card-delimiter', this._CSS.block)
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
public save (): DelimiterData {
|
|
||||||
return {
|
|
||||||
variant: this._variant
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get toolbox () {
|
|
||||||
return { icon, title }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Delimiter
|
|
@ -1,106 +0,0 @@
|
|||||||
import { ContentlessBlock, BlockToolArgs } from './contentless-block'
|
|
||||||
import icon from '../assets/editor/charges-circle.svg.txt'
|
|
||||||
|
|
||||||
const title = 'DnDStats'
|
|
||||||
|
|
||||||
interface DnDStatsData {
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DnDStats extends ContentlessBlock {
|
|
||||||
static _toolboxConfig = { icon, title }
|
|
||||||
private _stats = [10, 10, 10, 10, 10, 10]
|
|
||||||
|
|
||||||
constructor (args: BlockToolArgs) {
|
|
||||||
super(args)
|
|
||||||
this.data = args.data as DnDStatsData
|
|
||||||
this._element = this._render()
|
|
||||||
}
|
|
||||||
|
|
||||||
public get data () {
|
|
||||||
return {
|
|
||||||
text: this._stats.join(',')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public set data (data: DnDStatsData) {
|
|
||||||
if (data.text === undefined) data.text = ''
|
|
||||||
|
|
||||||
const newStats = data.text.split(',')
|
|
||||||
.map(x => parseInt(x, 10))
|
|
||||||
.filter(x => !Number.isNaN(x))
|
|
||||||
|
|
||||||
while (newStats.length < 6) newStats.push(10) // fill missing stats
|
|
||||||
|
|
||||||
this._stats = newStats
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates a random four character long id
|
|
||||||
private randomId (): string {
|
|
||||||
const min = 46656 // '1000'
|
|
||||||
const max = 1679615 /* 'zzzz' */ - 46656 /* '1000' */
|
|
||||||
return (min + Math.floor(max * Math.random())).toString(36)
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderStatMod (value: number): string {
|
|
||||||
const mod = Math.floor((value - 10) / 2.0)
|
|
||||||
const sign = mod < 0 ? '' : '+'
|
|
||||||
return ` (${sign}${mod})`
|
|
||||||
}
|
|
||||||
|
|
||||||
private createStatBlock (title: string, value: number, changeHandler: (newValue: number) => void): HTMLElement {
|
|
||||||
const id = `dnd-stat-${title}-${this.randomId()}`
|
|
||||||
|
|
||||||
const labelWrapper = document.createElement('label')
|
|
||||||
const titleEl = document.createElement('span')
|
|
||||||
const statInputEl = document.createElement('input')
|
|
||||||
const statModEl = document.createElement('span')
|
|
||||||
|
|
||||||
// should allow focussing block with tab
|
|
||||||
labelWrapper.setAttribute('z-index', '1')
|
|
||||||
labelWrapper.classList.add('dnd-stat-block')
|
|
||||||
labelWrapper.setAttribute('for', id)
|
|
||||||
|
|
||||||
titleEl.classList.add('dnd-stat-title')
|
|
||||||
titleEl.innerText = title
|
|
||||||
|
|
||||||
statInputEl.id = id
|
|
||||||
statInputEl.value = `${value}`
|
|
||||||
statInputEl.addEventListener('input', () => {
|
|
||||||
const value = parseInt(statInputEl.value, 10)
|
|
||||||
statModEl.innerText = this.renderStatMod(value)
|
|
||||||
changeHandler(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
statModEl.innerText = this.renderStatMod(value)
|
|
||||||
|
|
||||||
labelWrapper.appendChild(titleEl)
|
|
||||||
labelWrapper.appendChild(statInputEl)
|
|
||||||
labelWrapper.appendChild(statModEl)
|
|
||||||
|
|
||||||
return labelWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _render (): HTMLElement {
|
|
||||||
const el = document.createElement('div')
|
|
||||||
el.classList.add('card-dnd-stats')
|
|
||||||
const stats = this._stats || [10, 10, 10, 10, 10, 10]
|
|
||||||
const titles = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']
|
|
||||||
|
|
||||||
stats.forEach((stat, i) => {
|
|
||||||
const title = titles[i]
|
|
||||||
const block = this.createStatBlock(title, stat, newValue => {
|
|
||||||
this._stats[i] = newValue
|
|
||||||
})
|
|
||||||
el.appendChild(block)
|
|
||||||
})
|
|
||||||
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
public save (): DnDStatsData {
|
|
||||||
return this.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DnDStats
|
|
@ -1,159 +0,0 @@
|
|||||||
import {
|
|
||||||
ContentBlock,
|
|
||||||
ContentBlockArgs,
|
|
||||||
ContentBlockConfig,
|
|
||||||
ContentBlockData
|
|
||||||
} from './content-block'
|
|
||||||
|
|
||||||
import icon from '../assets/editor/header.svg.txt'
|
|
||||||
import icon1 from '../assets/editor/header1.svg.txt'
|
|
||||||
import icon2 from '../assets/editor/header2.svg.txt'
|
|
||||||
import icon3 from '../assets/editor/header3.svg.txt'
|
|
||||||
import icon4 from '../assets/editor/header4.svg.txt'
|
|
||||||
import icon5 from '../assets/editor/header5.svg.txt'
|
|
||||||
import icon6 from '../assets/editor/header6.svg.txt'
|
|
||||||
|
|
||||||
const title = 'Heading'
|
|
||||||
|
|
||||||
enum HeadingLevel {
|
|
||||||
One = 1,
|
|
||||||
Two = 2,
|
|
||||||
Three = 3,
|
|
||||||
Four = 4,
|
|
||||||
Five = 5,
|
|
||||||
Six = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
const icons = [null, icon1, icon2, icon3, icon4, icon5, icon6]
|
|
||||||
|
|
||||||
interface HeadingConfig extends ContentBlockConfig {
|
|
||||||
placeholder?: string;
|
|
||||||
levels?: HeadingLevel[];
|
|
||||||
defaultLevel?: HeadingLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HeadingData extends ContentBlockData {
|
|
||||||
text: string;
|
|
||||||
level?: HeadingLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Heading extends ContentBlock {
|
|
||||||
static _supportedTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']
|
|
||||||
static _toolboxConfig = { icon, title }
|
|
||||||
|
|
||||||
protected _config: HeadingConfig
|
|
||||||
private defaultLevel: HeadingLevel
|
|
||||||
private currentLevel: HeadingLevel
|
|
||||||
|
|
||||||
constructor (args: ContentBlockArgs) {
|
|
||||||
super(args)
|
|
||||||
this._config = args.config as HeadingConfig
|
|
||||||
|
|
||||||
if (this._config.levels === undefined) {
|
|
||||||
this._config.levels = [HeadingLevel.Two, HeadingLevel.Three]
|
|
||||||
}
|
|
||||||
if (this._config.defaultLevel === undefined) {
|
|
||||||
this._config.defaultLevel = HeadingLevel.Two
|
|
||||||
}
|
|
||||||
if (this._config.levels.indexOf(this._config.defaultLevel) === -1) {
|
|
||||||
console.warn('(ง\'̀-\'́)ง Heading Tool: the default level specified was not found in available levels')
|
|
||||||
}
|
|
||||||
this.defaultLevel = this._config.defaultLevel
|
|
||||||
this.currentLevel = this.defaultLevel
|
|
||||||
|
|
||||||
// setting data will rerender the element with the right settings
|
|
||||||
this.data = {
|
|
||||||
level: this.currentLevel,
|
|
||||||
text: (args.data as HeadingData).text || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
this._settingButtons = this._config.levels.map(level => {
|
|
||||||
return {
|
|
||||||
name: `H${level}`,
|
|
||||||
icon: icons[level] || icon,
|
|
||||||
action: (name: string) => this.setLevel(name),
|
|
||||||
isActive: (name: string): boolean => this.isCurrentLevel(name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public get data (): HeadingData {
|
|
||||||
return this._data as HeadingData
|
|
||||||
}
|
|
||||||
|
|
||||||
public set data (data: HeadingData) {
|
|
||||||
const currentData = this._data as HeadingData
|
|
||||||
|
|
||||||
if (data.level === undefined) data.level = currentData.level || this.defaultLevel
|
|
||||||
if (data.text === undefined) data.text = currentData.text || ''
|
|
||||||
|
|
||||||
this._data = data
|
|
||||||
this.currentLevel = data.level
|
|
||||||
|
|
||||||
const newHeader = this._render()
|
|
||||||
if (this._element.parentNode) {
|
|
||||||
this._element.parentNode.replaceChild(newHeader, this._element)
|
|
||||||
}
|
|
||||||
this._element = newHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
private isCurrentLevel (name: string): boolean {
|
|
||||||
const currentLevel = `H${this.currentLevel}`
|
|
||||||
return name === currentLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
private setLevel (name: string) {
|
|
||||||
const level = parseInt(name[1], 10)
|
|
||||||
this.data = { level, text: this._element.innerHTML }
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _render (): HTMLElement {
|
|
||||||
const el = document.createElement(`H${this.currentLevel}`)
|
|
||||||
el.innerHTML = this.data.text || ''
|
|
||||||
el.classList.add(this._CSS.block)
|
|
||||||
el.contentEditable = 'true'
|
|
||||||
el.dataset.placeholder = this._config.placeholder || ''
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pasted H1-H6 tags to substitute with header tool
|
|
||||||
protected pasteHandler (element: HTMLHeadingElement): HeadingData {
|
|
||||||
const text = element.innerHTML
|
|
||||||
let level = this.defaultLevel
|
|
||||||
|
|
||||||
const tagMatch = element.tagName.match(/H(\d)/)
|
|
||||||
if (tagMatch) level = parseInt(tagMatch[1], 10)
|
|
||||||
|
|
||||||
// Fallback to nearest level when specified not available
|
|
||||||
if (this._config.levels) {
|
|
||||||
level = this._config.levels.reduce((prevLevel, currLevel) => {
|
|
||||||
return Math.abs(currLevel - level) < Math.abs(prevLevel - level) ? currLevel : prevLevel
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return { level, text }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method that specified how to merge two Text blocks.
|
|
||||||
// Called by Editor.js by backspace at the beginning of the Block
|
|
||||||
public merge (data: HeadingData) {
|
|
||||||
this.data = {
|
|
||||||
text: this.data.text + (data.text || ''),
|
|
||||||
level: this.data.level
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract tools data from view
|
|
||||||
public save (toolsContent: HTMLElement): HeadingData {
|
|
||||||
return {
|
|
||||||
text: toolsContent.innerHTML,
|
|
||||||
level: this.currentLevel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get sanitize () {
|
|
||||||
return { level: {} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Heading
|
|
@ -1,4 +0,0 @@
|
|||||||
export { default as Delimiter } from './delimiter'
|
|
||||||
export { default as Heading } from './heading'
|
|
||||||
export { default as Charges } from './charges'
|
|
||||||
export { default as DnDStats } from './dnd-stats'
|
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Node } from 'tiptap'
|
||||||
|
import { tableNodes, tableEditing, goToNextCell, deleteTable } from 'prosemirror-tables'
|
||||||
|
import { createTable } from 'prosemirror-utils'
|
||||||
|
import { TextSelection } from 'prosemirror-state'
|
||||||
|
|
||||||
|
export default class StatBlock extends Node {
|
||||||
|
get name () {
|
||||||
|
return 'stat_block'
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultOptions () {
|
||||||
|
return {
|
||||||
|
resizable: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get schema () {
|
||||||
|
return {
|
||||||
|
group: 'block',
|
||||||
|
content: 'stat_column+',
|
||||||
|
toDOM: () => ['ol', { 'data-type': this.name }, 0],
|
||||||
|
parseDOM: [{
|
||||||
|
priority: 51,
|
||||||
|
tag: `[data-type="${this.name}"]`
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands ({ schema }) {
|
||||||
|
return () => (state, dispatch) => {
|
||||||
|
const offset = state.tr.selection.anchor + 1
|
||||||
|
|
||||||
|
const nodes = createTable(schema, 2, 6, true)
|
||||||
|
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView()
|
||||||
|
const resolvedPos = tr.doc.resolve(offset)
|
||||||
|
|
||||||
|
tr.setSelection(TextSelection.near(resolvedPos))
|
||||||
|
|
||||||
|
dispatch(tr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keys () {
|
||||||
|
return {
|
||||||
|
Tab: goToNextCell(1),
|
||||||
|
'Shift-Tab': goToNextCell(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get plugins () {
|
||||||
|
return [tableEditing()]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { Node } from 'tiptap'
|
||||||
|
import { tableEditing, goToNextCell, deleteTable } from 'prosemirror-tables'
|
||||||
|
import { createTable } from 'prosemirror-utils'
|
||||||
|
import { TextSelection } from 'prosemirror-state'
|
||||||
|
import { TableNodes } from 'tiptap-extensions'
|
||||||
|
|
||||||
|
export default class StatBlock extends Node {
|
||||||
|
public get name () {
|
||||||
|
return 'stat_block'
|
||||||
|
}
|
||||||
|
|
||||||
|
public get defaultOptions () {
|
||||||
|
return {
|
||||||
|
resizable: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get schema () {
|
||||||
|
return TableNodes.table
|
||||||
|
}
|
||||||
|
|
||||||
|
public commands ({ schema }) {
|
||||||
|
return {
|
||||||
|
createStatBlock: () => (state, dispatch) => {
|
||||||
|
const offset = state.tr.selection.anchor + 1
|
||||||
|
|
||||||
|
const nodes = createTable(schema, 2, 6, true)
|
||||||
|
const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView()
|
||||||
|
const resolvedPos = tr.doc.resolve(offset)
|
||||||
|
|
||||||
|
tr.setSelection(TextSelection.near(resolvedPos))
|
||||||
|
|
||||||
|
dispatch(tr)
|
||||||
|
},
|
||||||
|
deleteTable: () => deleteTable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public keys () {
|
||||||
|
return {
|
||||||
|
Tab: goToNextCell(1),
|
||||||
|
'Shift-Tab': goToNextCell(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get plugins () {
|
||||||
|
return [tableEditing()]
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
declare module '*.vue' {
|
|
||||||
import Vue from 'vue'
|
|
||||||
export default Vue
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.txt' {
|
|
||||||
const content: string
|
|
||||||
export default content
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@editorjs/paragraph'
|
|
||||||
declare module '@editorjs/list'
|
|
@ -0,0 +1,104 @@
|
|||||||
|
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||||
|
declare module 'tiptap-extensions' {
|
||||||
|
import { Node, Mark, Extension } from 'tiptap'
|
||||||
|
|
||||||
|
export class Heading extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Blockquote extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class BulletList extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Code extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class CodeBlock extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class CodeBlockHighlight extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class HardBreak extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class HorizontalRule extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Image extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class ListItem extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Mention extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class OrderedList extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Table extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TableCell extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TableHeader extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TableNodes extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TableRow extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TodoItem extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TodoList extends Node {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Link extends Mark {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Bold extends Mark {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Italic extends Mark {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Strike extends Mark {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Underline extends Mark {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Collaboration extends Extension {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Focus extends Extension {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class History extends Extension {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Placeholder extends Extension {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Search extends Extension {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class TrailingNode extends Extension {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Suggestions {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class Highlight {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
/* eslint @typescript-eslint/no-explicit-any: 0 */
|
||||||
|
declare module 'tiptap' {
|
||||||
|
import { Vue } from 'vue/types/vue'
|
||||||
|
|
||||||
|
export class Emitter {
|
||||||
|
on(event: any, fn: (...args: any[]) => void): Emitter
|
||||||
|
emit(event: any, ...args: any[]): Emitter
|
||||||
|
off(event: any, fn: (...args: any[]) => void): Emitter
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Editor extends Emitter {
|
||||||
|
constructor (...arg: any[])
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Extension {
|
||||||
|
constructor(...arg: any[])
|
||||||
|
init(): null
|
||||||
|
bindEditor(editor: Editor): void
|
||||||
|
get name(): string|null
|
||||||
|
get type(): string
|
||||||
|
get update(): object
|
||||||
|
get defaultOptions(): object
|
||||||
|
get plugins(): any[]
|
||||||
|
inputRules(): any[]
|
||||||
|
pasteRules(): any[]
|
||||||
|
keys(): object
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Node extends Extension {
|
||||||
|
get type(): 'node'
|
||||||
|
get view(): null
|
||||||
|
get schema(): null
|
||||||
|
command(): () => object
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Mark extends Extension {
|
||||||
|
get type(): 'mark'
|
||||||
|
get view(): null
|
||||||
|
get schema(): null
|
||||||
|
command(): () => object
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EditorMenuBubble extends Vue {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class EditorContent extends Vue {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class EditorMenuBar extends Vue {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
export class EditorFloatingMenu extends Vue {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import Vue from 'vue'
|
||||||
|
export default Vue
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
chainWebpack: config => {
|
|
||||||
config.module
|
|
||||||
.rule('raw')
|
|
||||||
.test(/\.txt$/)
|
|
||||||
.use('raw-loader')
|
|
||||||
.loader('raw-loader')
|
|
||||||
.end()
|
|
||||||
}
|
|
||||||
}
|
|