Browse Source

remove home services

Pi 4 weeks ago
parent
commit
370a70258c
100 changed files with 0 additions and 4053 deletions
  1. 0 5
      Dockerfile
  2. 0 6
      activities/sqlpage/migrations/001_activities.sql
  3. 0 5
      activities/sqlpage/sqlpage.json
  4. 0 114
      activities/www/chart.sql
  5. 0 93
      activities/www/index.sql
  6. 0 14
      css/clip.css
  7. 0 20
      css/code.css
  8. 0 9
      css/goto.css
  9. 0 470
      css/grids-responsive-min.css
  10. 0 17
      css/responsive-visibility-collapse.css
  11. 0 17
      css/responsive-visibility.css
  12. 0 9
      css/upload.css
  13. 0 5
      home-sqlpage.json
  14. 0 94
      home-sqlpage/index.sql
  15. 0 211
      home-sqlpage/sqlpage/migrations/000_index.sql
  16. 0 26
      nginx.conf
  17. 0 7
      rest/Dockerfile
  18. 0 18
      rest/__init__.py
  19. 0 14
      rest/bar.py
  20. 0 15
      rest/cherrypy.py
  21. 0 1
      rest/dev-requirements.txt
  22. 0 181
      rest/hash_util.py
  23. 0 166
      rest/pyapi.py
  24. 0 36
      rest/qr.py
  25. 0 7
      rest/requirements.txt
  26. BIN
      rest/static/clip/144.png
  27. BIN
      rest/static/clip/192.png
  28. BIN
      rest/static/clip/48.png
  29. BIN
      rest/static/clip/512.png
  30. BIN
      rest/static/clip/72.png
  31. BIN
      rest/static/clip/96.png
  32. 0 9
      rest/static/clip/clip-favicon.svg
  33. 0 93
      rest/static/clip/clip-favicon_square.svg
  34. 0 61
      rest/static/clip/manifest.json
  35. 0 1
      rest/static/clip/qr.svg
  36. 0 1
      rest/static/code/qr.svg
  37. BIN
      rest/static/ftark-open.png
  38. 0 138
      rest/static/ftark-open.svg
  39. BIN
      rest/static/goto/144.png
  40. BIN
      rest/static/goto/192.png
  41. BIN
      rest/static/goto/48.png
  42. BIN
      rest/static/goto/512.png
  43. BIN
      rest/static/goto/72.png
  44. BIN
      rest/static/goto/96.png
  45. 0 69
      rest/static/goto/chain-link-3-2.svg
  46. 0 49
      rest/static/goto/chain-link-cut-away.svg
  47. 0 50
      rest/static/goto/chain-link.svg
  48. 0 62
      rest/static/goto/chain-link2-3-2.svg
  49. 0 51
      rest/static/goto/chain-link2-cut-away.svg
  50. 0 51
      rest/static/goto/chain-link2.svg
  51. 0 76
      rest/static/goto/chain-link2fr-3-2.svg
  52. 0 75
      rest/static/goto/chain-link2r-3-2.svg
  53. 0 61
      rest/static/goto/manifest.json
  54. 0 1
      rest/static/goto/qr.svg
  55. BIN
      rest/static/shandanone-small.png
  56. 0 68
      rest/static/shandanone-small.svg
  57. BIN
      rest/static/shandanone2.png
  58. 0 78
      rest/static/shandanone2.svg
  59. BIN
      rest/static/upload/144.png
  60. BIN
      rest/static/upload/192.png
  61. BIN
      rest/static/upload/48.png
  62. BIN
      rest/static/upload/512.png
  63. BIN
      rest/static/upload/72.png
  64. BIN
      rest/static/upload/96.png
  65. 0 61
      rest/static/upload/manifest.json
  66. 0 1
      rest/static/upload/qr.svg
  67. 0 46
      rest/static/upload/upload-favicon.svg
  68. 0 63
      rest/static/upload/upload-favicon_maskable.svg
  69. 0 65
      rest/static/upload/upload-favicon_square.svg
  70. 0 5
      rest/tool_color.py
  71. 0 146
      rest/validate.py
  72. 0 25
      robots.txt
  73. 0 0
      test/__init__.py
  74. 0 112
      test/rest/rfc2396.py
  75. 0 308
      test/rest/test_hash_util.py
  76. 0 72
      test/rest/test_url.py
  77. 0 8
      util-sqlpage/clip/Index.sql
  78. 0 47
      util-sqlpage/clip/form.sql
  79. 0 28
      util-sqlpage/clip/index.sql
  80. 0 8
      util-sqlpage/clip/save.sql
  81. 0 11
      util-sqlpage/code/Index.sql
  82. 0 67
      util-sqlpage/code/form-fuel.sql
  83. 0 59
      util-sqlpage/code/form.sql
  84. 0 16
      util-sqlpage/code/index.sql
  85. 0 24
      util-sqlpage/code/json/filters.json
  86. 0 9
      util-sqlpage/code/new.sql
  87. 0 52
      util-sqlpage/code/recent.sql
  88. 0 12
      util-sqlpage/code/save.sql
  89. 0 12
      util-sqlpage/code/update.sql
  90. 0 8
      util-sqlpage/goto/Index.sql
  91. 0 36
      util-sqlpage/goto/form.sql
  92. 0 32
      util-sqlpage/goto/index.sql
  93. 0 12
      util-sqlpage/goto/preview.sql
  94. 0 11
      util-sqlpage/goto/redirect.sql
  95. 0 9
      util-sqlpage/goto/save.sql
  96. 0 22
      util-sqlpage/sqlpage/Link.sql
  97. 0 46
      util-sqlpage/sqlpage/Open.sql
  98. 0 11
      util-sqlpage/sqlpage/QR.sql
  99. 0 50
      util-sqlpage/sqlpage/Style.sql
  100. 0 5
      util-sqlpage/sqlpage/alert.sql

+ 0 - 5
Dockerfile

@@ -1,5 +0,0 @@
-FROM nginx:alpine
-COPY nginx.conf /etc/nginx/nginx.conf
-COPY prayer-generator/license /usr/share/nginx/html/license
-COPY css /usr/share/nginx/html/css
-COPY robots.txt /usr/share/nginx/html

+ 0 - 6
activities/sqlpage/migrations/001_activities.sql

@@ -1,6 +0,0 @@
-DROP TABLE IF EXISTS activities;
-CREATE TABLE activities (
-  user TEXT,
-  ts TIMESTAMP,
-  activity TEXT
-);

+ 0 - 5
activities/sqlpage/sqlpage.json

@@ -1,5 +0,0 @@
-{
-  "database_url": "sqlite://./activities.db",
-  "site_prefix": "/activity",
-  "max_database_pool_connections": 16
-}

+ 0 - 114
activities/www/chart.sql

@@ -1,114 +0,0 @@
-SET ":user" = COALESCE(:user, $user, '');
-SET ":date" = COALESCE(:date, $date, CURRENT_TIMESTAMP);
-SET ":start_of_day" = (SELECT datetime(datetime(:date, 'localtime'), 'start of day'));
-SET ":end_of_day" = (SELECT datetime(:start_of_day, '+1 days'));
-
-SET ":total_time" = (WITH delta AS (SELECT julianday(COALESCE((
-  SELECT min(lag.ts, datetime(:end_of_day, 'utc'))
-  FROM activities lag
-  WHERE lag.ts > a.ts AND lag.activity <> a.activity
-  ORDER BY lag.ts ASC
-  LIMIT 1
-), min(CURRENT_TIMESTAMP, datetime(:end_of_day, 'utc')))) - julianday(a.ts) AS delta
-, activity
-FROM activities a
-WHERE user = :user
-AND datetime(datetime(ts, 'localtime'), 'start of day') = :start_of_day)
-SELECT printf("%.0f", sum(delta)*60*24) AS minutes --, activity
-FROM delta
-WHERE activity = 'Eating'
-GROUP BY delta.activity);
-
-SELECT 'chart' AS component
---, '' AS title
-, :total_time || ' min.' AS title
-, TRUE AS time
-, 'area' AS type
-, 1 AS ymax
-, 0 AS ymin
-, 1 AS ystep
-, '100' AS height
--- TODO: fix color to series
---
---  SELECT DISTINCT color(activity) FROM activities ORDER BY activity;
-, 'azure' AS color
-, 'green' AS color
-, 'red' AS color
-;
-SET ":any" = EXISTS (SELECT * FROM activities
-    WHERE user = :user
-    AND datetime(ts, 'localtime')
-    BETWEEN :start_of_day AND :end_of_day
-    LIMIT 1
-);
-SELECT NULL AS x, NULL AS y, 'Unknown' AS series WHERE :any;
-SELECT NULL AS x, NULL AS y, 'Eating' AS series WHERE :any;
-SELECT NULL AS x, NULL AS y, 'Not eating' AS series WHERE :any;
-SET ":morning_offset" = '08:00:00.000';
-SET ":evening_offset" = '03:00:00.000';
-WITH plot AS (
-    SELECT ROW_NUMBER() OVER (ORDER BY ts) AS row
-    , ts
-    , activity
-    FROM (
-        SELECT ts, activity FROM activities
-        WHERE user = :user
-        AND datetime(ts, 'localtime') BETWEEN :start_of_day AND :end_of_day
-        UNION
-        SELECT datetime(:start_of_day, 'utc'), (
-            SELECT activity FROM activities
-            WHERE user = :user AND datetime(ts, 'localtime') <= :start_of_day
-            AND :any
-            ORDER BY ts DESC
-            LIMIT 1
-        )
-        UNION
-        SELECT datetime(:end_of_day, 'utc'), (
-            SELECT activity FROM activities
-            WHERE user = :user AND datetime(ts, 'localtime') <= :end_of_day
-            AND :any
-            ORDER BY ts DESC
-            LIMIT 1
-        )
-    )
-)
-SELECT datetime(:start_of_day, '+'||:morning_offset) AS x, 1 AS y, (
-    SELECT activity FROM activities
-    WHERE user = :user AND datetime(ts, 'localtime') <= datetime(:start_of_day, '+'||:morning_offset)
-    AND :any
-    ORDER BY ts DESC
-    LIMIT 1
-) AS series
-UNION
-SELECT datetime(:end_of_day, '-'||:evening_offset) AS x, 1 AS y, (
-    SELECT activity FROM activities
-    WHERE user = :user AND datetime(ts, 'localtime') <= datetime(:end_of_day, '-'||:evening_offset)
-    AND :any
-    ORDER BY ts DESC
-    LIMIT 1
-) AS series
-UNION
-SELECT datetime(x, 'localtime') AS x, CASE value WHEN 0 THEN NULL ELSE value END AS y, series FROM (
-    SELECT datetime(next.ts, '-00:00:01.000') AS x, 0 AS value, next.activity AS series
-    FROM plot, plot next
-    WHERE plot.row = next.row - 1
-    AND plot.activity <> next.activity
-    UNION
-    SELECT ts AS x, 1 AS value, activity AS series
-    FROM plot
-    UNION
-    SELECT datetime(next.ts, '-00:00:01.000') AS x, 1 AS value, plot.activity AS series
-    FROM plot, plot next
-    WHERE plot.row = next.row - 1
-    AND plot.activity <> next.activity
-    UNION
-    SELECT next.ts AS x, 0 AS value, plot.activity AS series
-    FROM plot, plot next
-    WHERE plot.row = next.row - 1
-    AND plot.activity <> next.activity
-) q
-WHERE
-    datetime(datetime(x, 'localtime'), '-'||:morning_offset) >= :start_of_day
-AND
-    datetime(datetime(x, 'localtime'), '+'||:evening_offset) <= :end_of_day
-;

+ 0 - 93
activities/www/index.sql

@@ -1,93 +0,0 @@
-SET ":user" = 'shannon';
-INSERT INTO activities(user, activity, ts)
-SELECT :user, :activity, COALESCE(CASE COALESCE(:ts, '')
-  WHEN '' THEN NULL
-  ELSE datetime(:ts, 'utc')
-END, CURRENT_TIMESTAMP)
-WHERE COALESCE(:activity, '') <> ''
-RETURNING 'redirect' AS component, '/activity' AS link
-;
-
-SET ":title" = 'Activities';
-SELECT 'shell' AS component
-, 'dark' AS theme
-, :title AS title
-, 'fluid' AS layout
-, '/activity' AS link
-;
-
-SELECT 'text' AS component
-, '<style>
-.card > .card-body:has(.card-body > .d-flex + .chart) {
-    margin-top: 0 !important;
-    padding: 0;
-}
-.card > .card-body:has(.card-body > .d-flex + .chart) > .card-title {
-    position: absolute;
-    z-index: 99;
-    margin: 0;
-    padding: 0 10px;
-}
-.card > .card-body:has(> .d-flex + .chart) {
-    padding: 0;
-}
-.card > .card-body > .d-flex {
-    font-size: 0.3em;
-    padding: 0 120px;
-}
-.card > .card-body > .d-flex {
-    height: 0;
-}
-.col:has(+ .col .apexcharts-legend-text) .card > .card-body:has(> .d-flex + .chart) {
-    max-height: 80px;
-}
-.apexcharts-yaxis {
-    display: none;
-}
-.col:has(+ .col .apexcharts-legend-text) .apexcharts-legend {
-    /* height: 0; // modifying the height alters the chart size */
-    visibility: hidden;
-}
-</style>' AS html
-;
-
-SELECT 'form' AS component
-, '/activity/index.sql' AS action
-;
-SELECT 'select' AS type
-, 'Activity' AS label
-, 'activity' AS name
-, TRUE AS create_new
-, TRUE AS dropdown
-, (SELECT json_group_array(json_object(
-    'label', activity,
-    'value', activity,
-    'selected', activity = 'Unknown'
-  )) FROM activities WHERE user = :user
-) AS options
-, 7 AS width
-;
-SELECT 'datetime-local' AS type
-, 'ts' AS name
-, 'Date and Time' AS label
-, '' AS value
-, 4 AS width
-;
-
-SELECT 'button' AS component
-SELECT 'Week' AS title
-, 1 AS width
-, '/activity?week=week' AS link
-;
-
-SELECT 'card' AS component
-, 1 AS columns
-;
-
-SELECT date(datetime(CURRENT_TIMESTAMP, '-'|| value ||'  days'), 'localtime') AS title
-, '/activity/chart.sql'||
-'?user='||:user||
-'&date='|| REPLACE(datetime(CURRENT_TIMESTAMP, '-'|| value ||'  days'), ' ', '+') ||
-'&_sqlpage_embed' AS embed
-FROM json_each(json(CASE $week WHEN 'week' THEN '[0, 1, 2, 3, 4, 5, 6, 7]' ELSE '[0, 1]' END))
-;

+ 0 - 14
css/clip.css

@@ -1,14 +0,0 @@
-body .pure-g {
-    text-align: center
-}
-details svg, details img {
-    background-color: white
-}
-textarea.form-control {
-  font-family: FreeMono, monospace;
-}
-textarea.form-control[disabled] {
-  background-color: var(--tblr-bg-forms);
-  color: var(--tblr-body-color);
-  border-color: #2fb344;
-}

+ 0 - 20
css/code.css

@@ -1,20 +0,0 @@
-body .pure-g {
-    text-align: center
-}
-details svg, details img {
-    background-color: white
-}
-.row-cols-lg-2:has(.bg-azure) {
-  justify-content: center;
-}
-.card-img-top {
-    width: 90%;
-    margin: 5%;
-}
-table td, table th {
-   padding-left: 5px;
-   padding-right: 5px;
-   padding-top: 2px;
-   padding-bottom: 2px;
-   border-width: 1px;
-}

+ 0 - 9
css/goto.css

@@ -1,9 +0,0 @@
-body .pure-g {
-    text-align: center
-}
-details svg, details img {
-    background-color: white
-}
-.row-cols-lg-2:has(.bg-google) {
-  justify-content: center;
-}

+ 0 - 470
css/grids-responsive-min.css

@@ -1,470 +0,0 @@
-/* !
-Pure v2.1.0
-Copyright 2013 Yahoo!
-Licensed under the BSD License.
-https://github.com/pure-css/pure/blob/master/LICENSE */
-@media screen and (min-width:35.5em){
-.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-12,.pure-u-sm-1-2,.pure-u-sm-1-24,.pure-u-sm-1-3,.pure-u-sm-1-4,.pure-u-sm-1-5,.pure-u-sm-1-6,.pure-u-sm-1-8,.pure-u-sm-10-24,.pure-u-sm-11-12,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-2-24,.pure-u-sm-2-3,.pure-u-sm-2-5,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24,.pure-u-sm-3-24,.pure-u-sm-3-4,.pure-u-sm-3-5,.pure-u-sm-3-8,.pure-u-sm-4-24,.pure-u-sm-4-5,.pure-u-sm-5-12,.pure-u-sm-5-24,.pure-u-sm-5-5,.pure-u-sm-5-6,.pure-u-sm-5-8,.pure-u-sm-6-24,.pure-u-sm-7-12,.pure-u-sm-7-24,.pure-u-sm-7-8,.pure-u-sm-8-24,.pure-u-sm-9-24{
-display:inline-block;
-letter-spacing:normal;
-word-spacing:normal;
-vertical-align:top;
-text-rendering:auto;
-}
-.pure-u-sm-1-24{
-width:4.1667%;
-}
-.pure-u-sm-1-12,.pure-u-sm-2-24{
-width:8.3333%;
-}
-.pure-u-sm-1-8,.pure-u-sm-3-24{
-width:12.5%;
-}
-.pure-u-sm-1-6,.pure-u-sm-4-24{
-width:16.6667%;
-}
-.pure-u-sm-1-5{
-width:20%;
-}
-.pure-u-sm-5-24{
-width:20.8333%;
-}
-.pure-u-sm-1-4,.pure-u-sm-6-24{
-width:25%;
-}
-.pure-u-sm-7-24{
-width:29.1667%;
-}
-.pure-u-sm-1-3,.pure-u-sm-8-24{
-width:33.3333%;
-}
-.pure-u-sm-3-8,.pure-u-sm-9-24{
-width:37.5%;
-}
-.pure-u-sm-2-5{
-width:40%;
-}
-.pure-u-sm-10-24,.pure-u-sm-5-12{
-width:41.6667%;
-}
-.pure-u-sm-11-24{
-width:45.8333%;
-}
-.pure-u-sm-1-2,.pure-u-sm-12-24{
-width:50%;
-}
-.pure-u-sm-13-24{
-width:54.1667%;
-}
-.pure-u-sm-14-24,.pure-u-sm-7-12{
-width:58.3333%;
-}
-.pure-u-sm-3-5{
-width:60%;
-}
-.pure-u-sm-15-24,.pure-u-sm-5-8{
-width:62.5%;
-}
-.pure-u-sm-16-24,.pure-u-sm-2-3{
-width:66.6667%;
-}
-.pure-u-sm-17-24{
-width:70.8333%;
-}
-.pure-u-sm-18-24,.pure-u-sm-3-4{
-width:75%;
-}
-.pure-u-sm-19-24{
-width:79.1667%;
-}
-.pure-u-sm-4-5{
-width:80%;
-}
-.pure-u-sm-20-24,.pure-u-sm-5-6{
-width:83.3333%;
-}
-.pure-u-sm-21-24,.pure-u-sm-7-8{
-width:87.5%;
-}
-.pure-u-sm-11-12,.pure-u-sm-22-24{
-width:91.6667%;
-}
-.pure-u-sm-23-24{
-width:95.8333%;
-}
-.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-24-24,.pure-u-sm-5-5{
-width:100%;
-}
-}
-@media screen and (min-width:40em){
-.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-12,.pure-u-md-1-2,.pure-u-md-1-24,.pure-u-md-1-3,.pure-u-md-1-4,.pure-u-md-1-5,.pure-u-md-1-6,.pure-u-md-1-8,.pure-u-md-10-24,.pure-u-md-11-12,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-2-24,.pure-u-md-2-3,.pure-u-md-2-5,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24,.pure-u-md-3-24,.pure-u-md-3-4,.pure-u-md-3-5,.pure-u-md-3-8,.pure-u-md-4-24,.pure-u-md-4-5,.pure-u-md-5-12,.pure-u-md-5-24,.pure-u-md-5-5,.pure-u-md-5-6,.pure-u-md-5-8,.pure-u-md-6-24,.pure-u-md-7-12,.pure-u-md-7-24,.pure-u-md-7-8,.pure-u-md-8-24,.pure-u-md-9-24{
-display:inline-block;
-letter-spacing:normal;
-word-spacing:normal;
-vertical-align:top;
-text-rendering:auto;
-}
-.pure-u-md-1-24{
-width:4.1667%;
-}
-.pure-u-md-1-12,.pure-u-md-2-24{
-width:8.3333%;
-}
-.pure-u-md-1-8,.pure-u-md-3-24{
-width:12.5%;
-}
-.pure-u-md-1-6,.pure-u-md-4-24{
-width:16.6667%;
-}
-.pure-u-md-1-5{
-width:20%;
-}
-.pure-u-md-5-24{
-width:20.8333%;
-}
-.pure-u-md-1-4,.pure-u-md-6-24{
-width:25%;
-}
-.pure-u-md-7-24{
-width:29.1667%;
-}
-.pure-u-md-1-3,.pure-u-md-8-24{
-width:33.3333%;
-}
-.pure-u-md-3-8,.pure-u-md-9-24{
-width:37.5%;
-}
-.pure-u-md-2-5{
-width:40%;
-}
-.pure-u-md-10-24,.pure-u-md-5-12{
-width:41.6667%;
-}
-.pure-u-md-11-24{
-width:45.8333%;
-}
-.pure-u-md-1-2,.pure-u-md-12-24{
-width:50%;
-}
-.pure-u-md-13-24{
-width:54.1667%;
-}
-.pure-u-md-14-24,.pure-u-md-7-12{
-width:58.3333%;
-}
-.pure-u-md-3-5{
-width:60%;
-}
-.pure-u-md-15-24,.pure-u-md-5-8{
-width:62.5%;
-}
-.pure-u-md-16-24,.pure-u-md-2-3{
-width:66.6667%;
-}
-.pure-u-md-17-24{
-width:70.8333%;
-}
-.pure-u-md-18-24,.pure-u-md-3-4{
-width:75%;
-}
-.pure-u-md-19-24{
-width:79.1667%;
-}
-.pure-u-md-4-5{
-width:80%;
-}
-.pure-u-md-20-24,.pure-u-md-5-6{
-width:83.3333%;
-}
-.pure-u-md-21-24,.pure-u-md-7-8{
-width:87.5%;
-}
-.pure-u-md-11-12,.pure-u-md-22-24{
-width:91.6667%;
-}
-.pure-u-md-23-24{
-width:95.8333%;
-}
-.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-24-24,.pure-u-md-5-5{
-width:100%;
-}
-}
-@media screen and (min-width:64em){
-.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-12,.pure-u-lg-1-2,.pure-u-lg-1-24,.pure-u-lg-1-3,.pure-u-lg-1-4,.pure-u-lg-1-5,.pure-u-lg-1-6,.pure-u-lg-1-8,.pure-u-lg-10-24,.pure-u-lg-11-12,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-2-24,.pure-u-lg-2-3,.pure-u-lg-2-5,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24,.pure-u-lg-3-24,.pure-u-lg-3-4,.pure-u-lg-3-5,.pure-u-lg-3-8,.pure-u-lg-4-24,.pure-u-lg-4-5,.pure-u-lg-5-12,.pure-u-lg-5-24,.pure-u-lg-5-5,.pure-u-lg-5-6,.pure-u-lg-5-8,.pure-u-lg-6-24,.pure-u-lg-7-12,.pure-u-lg-7-24,.pure-u-lg-7-8,.pure-u-lg-8-24,.pure-u-lg-9-24{
-display:inline-block;
-letter-spacing:normal;
-word-spacing:normal;
-vertical-align:top;
-text-rendering:auto;
-}
-.pure-u-lg-1-24{
-width:4.1667%;
-}
-.pure-u-lg-1-12,.pure-u-lg-2-24{
-width:8.3333%;
-}
-.pure-u-lg-1-8,.pure-u-lg-3-24{
-width:12.5%;
-}
-.pure-u-lg-1-6,.pure-u-lg-4-24{
-width:16.6667%;
-}
-.pure-u-lg-1-5{
-width:20%;
-}
-.pure-u-lg-5-24{
-width:20.8333%;
-}
-.pure-u-lg-1-4,.pure-u-lg-6-24{
-width:25%;
-}
-.pure-u-lg-7-24{
-width:29.1667%;
-}
-.pure-u-lg-1-3,.pure-u-lg-8-24{
-width:33.3333%;
-}
-.pure-u-lg-3-8,.pure-u-lg-9-24{
-width:37.5%;
-}
-.pure-u-lg-2-5{
-width:40%;
-}
-.pure-u-lg-10-24,.pure-u-lg-5-12{
-width:41.6667%;
-}
-.pure-u-lg-11-24{
-width:45.8333%;
-}
-.pure-u-lg-1-2,.pure-u-lg-12-24{
-width:50%;
-}
-.pure-u-lg-13-24{
-width:54.1667%;
-}
-.pure-u-lg-14-24,.pure-u-lg-7-12{
-width:58.3333%;
-}
-.pure-u-lg-3-5{
-width:60%;
-}
-.pure-u-lg-15-24,.pure-u-lg-5-8{
-width:62.5%;
-}
-.pure-u-lg-16-24,.pure-u-lg-2-3{
-width:66.6667%;
-}
-.pure-u-lg-17-24{
-width:70.8333%;
-}
-.pure-u-lg-18-24,.pure-u-lg-3-4{
-width:75%;
-}
-.pure-u-lg-19-24{
-width:79.1667%;
-}
-.pure-u-lg-4-5{
-width:80%;
-}
-.pure-u-lg-20-24,.pure-u-lg-5-6{
-width:83.3333%;
-}
-.pure-u-lg-21-24,.pure-u-lg-7-8{
-width:87.5%;
-}
-.pure-u-lg-11-12,.pure-u-lg-22-24{
-width:91.6667%;
-}
-.pure-u-lg-23-24{
-width:95.8333%;
-}
-.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-24-24,.pure-u-lg-5-5{
-width:100%;
-}
-}
-@media screen and (min-width:80em){
-.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-12,.pure-u-xl-1-2,.pure-u-xl-1-24,.pure-u-xl-1-3,.pure-u-xl-1-4,.pure-u-xl-1-5,.pure-u-xl-1-6,.pure-u-xl-1-8,.pure-u-xl-10-24,.pure-u-xl-11-12,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-2-24,.pure-u-xl-2-3,.pure-u-xl-2-5,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24,.pure-u-xl-3-24,.pure-u-xl-3-4,.pure-u-xl-3-5,.pure-u-xl-3-8,.pure-u-xl-4-24,.pure-u-xl-4-5,.pure-u-xl-5-12,.pure-u-xl-5-24,.pure-u-xl-5-5,.pure-u-xl-5-6,.pure-u-xl-5-8,.pure-u-xl-6-24,.pure-u-xl-7-12,.pure-u-xl-7-24,.pure-u-xl-7-8,.pure-u-xl-8-24,.pure-u-xl-9-24{
-display:inline-block;
-letter-spacing:normal;
-word-spacing:normal;
-vertical-align:top;
-text-rendering:auto;
-}
-.pure-u-xl-1-24{
-width:4.1667%;
-}
-.pure-u-xl-1-12,.pure-u-xl-2-24{
-width:8.3333%;
-}
-.pure-u-xl-1-8,.pure-u-xl-3-24{
-width:12.5%;
-}
-.pure-u-xl-1-6,.pure-u-xl-4-24{
-width:16.6667%;
-}
-.pure-u-xl-1-5{
-width:20%;
-}
-.pure-u-xl-5-24{
-width:20.8333%;
-}
-.pure-u-xl-1-4,.pure-u-xl-6-24{
-width:25%;
-}
-.pure-u-xl-7-24{
-width:29.1667%;
-}
-.pure-u-xl-1-3,.pure-u-xl-8-24{
-width:33.3333%;
-}
-.pure-u-xl-3-8,.pure-u-xl-9-24{
-width:37.5%;
-}
-.pure-u-xl-2-5{
-width:40%;
-}
-.pure-u-xl-10-24,.pure-u-xl-5-12{
-width:41.6667%;
-}
-.pure-u-xl-11-24{
-width:45.8333%;
-}
-.pure-u-xl-1-2,.pure-u-xl-12-24{
-width:50%;
-}
-.pure-u-xl-13-24{
-width:54.1667%;
-}
-.pure-u-xl-14-24,.pure-u-xl-7-12{
-width:58.3333%;
-}
-.pure-u-xl-3-5{
-width:60%;
-}
-.pure-u-xl-15-24,.pure-u-xl-5-8{
-width:62.5%;
-}
-.pure-u-xl-16-24,.pure-u-xl-2-3{
-width:66.6667%;
-}
-.pure-u-xl-17-24{
-width:70.8333%;
-}
-.pure-u-xl-18-24,.pure-u-xl-3-4{
-width:75%;
-}
-.pure-u-xl-19-24{
-width:79.1667%;
-}
-.pure-u-xl-4-5{
-width:80%;
-}
-.pure-u-xl-20-24,.pure-u-xl-5-6{
-width:83.3333%;
-}
-.pure-u-xl-21-24,.pure-u-xl-7-8{
-width:87.5%;
-}
-.pure-u-xl-11-12,.pure-u-xl-22-24{
-width:91.6667%;
-}
-.pure-u-xl-23-24{
-width:95.8333%;
-}
-.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-24-24,.pure-u-xl-5-5{
-width:100%;
-}
-}
-@media screen and (min-width:120em){
-.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-1-12,.pure-u-xxl-1-2,.pure-u-xxl-1-24,.pure-u-xxl-1-3,.pure-u-xxl-1-4,.pure-u-xxl-1-5,.pure-u-xxl-1-6,.pure-u-xxl-1-8,.pure-u-xxl-10-24,.pure-u-xxl-11-12,.pure-u-xxl-11-24,.pure-u-xxl-12-24,.pure-u-xxl-13-24,.pure-u-xxl-14-24,.pure-u-xxl-15-24,.pure-u-xxl-16-24,.pure-u-xxl-17-24,.pure-u-xxl-18-24,.pure-u-xxl-19-24,.pure-u-xxl-2-24,.pure-u-xxl-2-3,.pure-u-xxl-2-5,.pure-u-xxl-20-24,.pure-u-xxl-21-24,.pure-u-xxl-22-24,.pure-u-xxl-23-24,.pure-u-xxl-24-24,.pure-u-xxl-3-24,.pure-u-xxl-3-4,.pure-u-xxl-3-5,.pure-u-xxl-3-8,.pure-u-xxl-4-24,.pure-u-xxl-4-5,.pure-u-xxl-5-12,.pure-u-xxl-5-24,.pure-u-xxl-5-5,.pure-u-xxl-5-6,.pure-u-xxl-5-8,.pure-u-xxl-6-24,.pure-u-xxl-7-12,.pure-u-xxl-7-24,.pure-u-xxl-7-8,.pure-u-xxl-8-24,.pure-u-xxl-9-24{
-display:inline-block;
-letter-spacing:normal;
-word-spacing:normal;
-vertical-align:top;
-text-rendering:auto;
-}
-.pure-u-xxl-1-24{
-width:4.1667%;
-}
-.pure-u-xxl-1-12,.pure-u-xxl-2-24{
-width:8.3333%;
-}
-.pure-u-xxl-1-8,.pure-u-xxl-3-24{
-width:12.5%;
-}
-.pure-u-xxl-1-6,.pure-u-xxl-4-24{
-width:16.6667%;
-}
-.pure-u-xxl-1-5{
-width:20%;
-}
-.pure-u-xxl-5-24{
-width:20.8333%;
-}
-.pure-u-xxl-1-4,.pure-u-xxl-6-24{
-width:25%;
-}
-.pure-u-xxl-7-24{
-width:29.1667%;
-}
-.pure-u-xxl-1-3,.pure-u-xxl-8-24{
-width:33.3333%;
-}
-.pure-u-xxl-3-8,.pure-u-xxl-9-24{
-width:37.5%;
-}
-.pure-u-xxl-2-5{
-width:40%;
-}
-.pure-u-xxl-10-24,.pure-u-xxl-5-12{
-width:41.6667%;
-}
-.pure-u-xxl-11-24{
-width:45.8333%;
-}
-.pure-u-xxl-1-2,.pure-u-xxl-12-24{
-width:50%;
-}
-.pure-u-xxl-13-24{
-width:54.1667%;
-}
-.pure-u-xxl-14-24,.pure-u-xxl-7-12{
-width:58.3333%;
-}
-.pure-u-xxl-3-5{
-width:60%;
-}
-.pure-u-xxl-15-24,.pure-u-xxl-5-8{
-width:62.5%;
-}
-.pure-u-xxl-16-24,.pure-u-xxl-2-3{
-width:66.6667%;
-}
-.pure-u-xxl-17-24{
-width:70.8333%;
-}
-.pure-u-xxl-18-24,.pure-u-xxl-3-4{
-width:75%;
-}
-.pure-u-xxl-19-24{
-width:79.1667%;
-}
-.pure-u-xxl-4-5{
-width:80%;
-}
-.pure-u-xxl-20-24,.pure-u-xxl-5-6{
-width:83.3333%;
-}
-.pure-u-xxl-21-24,.pure-u-xxl-7-8{
-width:87.5%;
-}
-.pure-u-xxl-11-12,.pure-u-xxl-22-24{
-width:91.6667%;
-}
-.pure-u-xxl-23-24{
-width:95.8333%;
-}
-.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-24-24,.pure-u-xxl-5-5{
-width:100%;
-}
-}

+ 0 - 17
css/responsive-visibility-collapse.css

@@ -1,17 +0,0 @@
-.opt-md,.opt-lg,.opt-xl,.opt-xxl { display: none; }
-.opt-all,.opt-sm { display: table-cell; }
-@media screen and (min-width:35.5em){
-    .opt-all,.opt-sm { display: table-cell; }
-}
-@media screen and (min-width:40em){
-    .opt-all,.opt-sm,.opt-md { display: table-cell; }
-}
-@media screen and (min-width:64em){
-    .opt-all,.opt-sm,.opt-md,.opt-lg { display: table-cell; }
-}
-@media screen and (min-width:80em){
-    .opt-all,.opt-sm,.opt-md,.opt-lg,.opt-xl { display: table-cell; }
-}
-@media screen and (min-width:120em){
-    .opt-all,.opt-sm,.opt-md,.opt-lg,.opt-xl,.opt-xxl { display: table-cell; }
-}

+ 0 - 17
css/responsive-visibility.css

@@ -1,17 +0,0 @@
-.opt-md,.opt-lg,.opt-xl,.opt-xxl { display: none; }
-.opt-all,.opt-sm { display: block; }
-@media screen and (min-width:35.5em){
-    .opt-all,.opt-sm { display: block; }
-}
-@media screen and (min-width:40em){
-    .opt-all,.opt-sm,.opt-md { display: block; }
-}
-@media screen and (min-width:64em){
-    .opt-all,.opt-sm,.opt-md,.opt-lg { display: block; }
-}
-@media screen and (min-width:80em){
-    .opt-all,.opt-sm,.opt-md,.opt-lg,.opt-xl { display: block; }
-}
-@media screen and (min-width:120em){
-    .opt-all,.opt-sm,.opt-md,.opt-lg,.opt-xl,.opt-xxl { display: block; }
-}

+ 0 - 9
css/upload.css

@@ -1,9 +0,0 @@
-body .pure-g {
-    text-align: center
-}
-details svg, details img {
-    background-color: white
-}
-.row-cols-lg-2:has(.bg-yellow) {
-  justify-content: center;
-}

+ 0 - 5
home-sqlpage.json

@@ -1,5 +0,0 @@
-{
-  "max_database_pool_connections": 16,
-  "database_url": "sqlite://:memory:",
-  "compress_responses": false
-}

+ 0 - 94
home-sqlpage/index.sql

@@ -1,94 +0,0 @@
-SELECT 'shell' AS component
-, 'dark' AS theme
-, 'Portal' AS title
-, '/static/shandanone-small.png' AS favicon
-, '/static/shandanone2.png' AS image
-, (SELECT json_object(
-    'title', 'Utilities',
-    'icon', 'calculator',
-    'submenu', json_group_array(json_object(
-      'title', title,
-      'icon', icon,
-      'link', link
-    ))
-  )
-  FROM sqlpage_cards
-  WHERE grouping = 2) AS menu_item
-, (SELECT json_object(
-    'title', 'Apps',
-    'icon', 'apps',
-    'submenu', json_group_array(json_object(
-      'title', title,
-      'icon', icon,
-      'link', link
-    ))
-  )
-  FROM sqlpage_cards
-  WHERE grouping = 3) AS menu_item
-, (SELECT json_object(
-    'title', 'RSS',
-    'icon', 'rss',
-    'submenu', json_group_array(json_object(
-      'title', title,
-      'icon', 'file-rss',
-      'link', link
-    ))
-  )
-  FROM sqlpage_cards
-  WHERE grouping = 4) AS menu_item
-;
-
-SELECT 'text' AS component
-, '<style>
-.card-img-top:has(+ .bg-rss) {
-    width: 40%;
-    margin: 10% 50% 10% 10%;
-}
-.card-img-top {
-    width: 40%;
-    margin: 10% 30% 10% 30%;
-}
-/* make row-cols-sm-2 behave like row-cols-2 */
-@media (max-width: 500px) {
-  .row-cols-sm-2>* {
-    flex: 0 0 auto;
-    width: 50%
-  }
-}
-</style>' AS html
-;
-SELECT 'card' AS component
-, 4 AS columns
-;
-SELECT title
-, link
-, top_image
-, description_md
-, color
-FROM sqlpage_cards
-WHERE grouping = 2
-;
-SELECT 'divider' AS component;
-SELECT 'card' AS component
-, 4 AS columns
-;
-SELECT title
-, link
-, top_image
-, description_md
-, color
-FROM sqlpage_cards
-WHERE grouping = 3
-;
-SELECT 'divider' AS component;
-SELECT 'card' AS component
-, 4 AS columns
-;
-SELECT title
-, link
-, 'https://upload.wikimedia.org/wikipedia/en/thumb/4/43/Feed-icon.svg/128px-Feed-icon.svg.png' AS top_image
-, description_md
-, 'rss' AS color
-FROM sqlpage_cards
-WHERE grouping = 4
-;

+ 0 - 211
home-sqlpage/sqlpage/migrations/000_index.sql

@@ -1,211 +0,0 @@
-DROP TABLE IF EXISTS sqlpage_cards;
-CREATE TABLE IF NOT EXISTS sqlpage_cards (
-	link text,
-	title text,
-	top_image text,
-	description_md text,
-	color text,
-	grouping int,
-	icon text
-);
-INSERT INTO sqlpage_cards
-VALUES
-(
-	'/feed/news/rss.html',
-	'News Feed',
-	NULL,
-	'Notable events but not weather or sports',
-	NULL,
-	4,
-	NULL
-),
-(
-	'/feed/discovery/rss.html',
-	'Discovery Feed',
-	NULL,
-	'Cool and inspirational articles',
-	NULL,
-	4,
-	NULL
-),
-(
-	'/feed/humour/rss.html',
-	'Humour Feed',
-	NULL,
-	'Humourous articles, memes or anything funny',
-	NULL,
-	4,
-	NULL
-),
-(
-	'/clip?',
-	'Clipboard',
-	'https://shandan.one/static/clip/clip-favicon_square.svg',
-	'Paste snippets to share via tiny URL',
-	'green',
-	2,
-	'clipboard'
-),
-(
-	'/goto?',
-	'Short URL',
-	'https://shandan.one/static/goto/chain-link2fr-3-2.svg',
-	'Make long URLs shorter and easy to type and say',
-	'pinterest',
-	2,
-	'link'
-),
-(
-	'/upload?',
-	'Share a File',
-	'https://shandan.one/static/upload/upload-favicon_square.svg',
-	'Upload a file and generate a short URL to share easily',
-	'yellow',
-	2,
-	'cloud-share'
-),
-(
-	'/code?',
-	'Track Vouchers',
-	'https://shandan.one/static/code/qr.svg',
-	'Save scanned voucher codes to keep them handy and not forgotten',
-	'azure',
-	2,
-	'barcode'
-),
-(
-	'https://mealie.shandan.one/g/das',
-	'Recipes',
-	'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-hidden="true" class="v-icon__svg" style="font-size: 100px; height: 100px; width: 100px; fill: rgb(229, 131, 37)"><path d="M8.1,13.34L3.91,9.16C2.35,7.59 2.35,5.06 3.91,3.5L10.93,10.5L8.1,13.34M13.41,13L20.29,19.88L18.88,21.29L12,14.41L5.12,21.29L3.71,19.88L13.36,10.22L13.16,10C12.38,9.23 12.38,7.97 13.16,7.19L17.5,2.82L18.43,3.74L15.19,7L16.15,7.94L19.39,4.69L20.31,5.61L17.06,8.85L18,9.81L21.26,6.56L22.18,7.5L17.81,11.84C17.03,12.62 15.77,12.62 15,11.84L14.78,11.64L13.41,13Z"></path></svg>',
-	'Share recipes and meal plans',
-	'yellow',
-	3,
-	'tools-kitchen-2'
-),
-(
-	'https://shandan.one/grocery/internal/apply.sql?groups[]=Fish,%20Meat,%20Eggs&apply=Apply&title=Trend',
-	'Trending Prices',
-	'https://shandan.one/grocery/favicon_square.svg',
-	'Plot charts showing historic price data',
-	'youtube',
-	3,
-	'shopping-cart'
-),
-(
-	'https://cropswap.shandan.one/',
-	'Taupiri Crop Swap',
-	'https://cropswap.shandan.one/user/themes/quark/images/favicon.png',
-	'Local crop sharing community',
-	'lime',
-	3,
-	'plant'
-),
-(
-	'https://mail.cropswap.shandan.one/',
-	'Crop Swap Mail',
-	'https://www.mhonarc.org/MHonArc/logo/xmhastampw_t.png.pagespeed.ic.JEPpZUA3TZ.webp',
-	'Member mailing list',
-	'github',
-	3,
-	'mail'
-),
-(
-	NULL, --'https://wol.shandan.one?location=outside',
-	'Wake a Device',
-	'data:image/svg+xml,<svg  xmlns="http://www.w3.org/2000/svg"  width="24"  height="24"  viewBox="0 0 24 24"  fill="none"  stroke="rgb(205,57,57)"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round"  class="icon icon-tabler icons-tabler-outline icon-tabler-power"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 6a7.75 7.75 0 1 0 10 0" /><path d="M12 4l0 8" /></svg>',
-	'Wake a device from [home](https://wol.shandan.one?location=home) or [outside](https://wol.shandan.one?location=outside)',
-	'red',
-	3,
-	'power'
-),
-(
-	'https://shandan.one/pgadmin4',
-	'pgAdmin',
-	'https://raw.githubusercontent.com/postgres/pgadmin4/c1ba645dceed5c9551a5f408e37a14d1041ee598/web/pgadmin/static/favicon.ico',
-	'Manage And access postgresql databases',
-	'azure',
-	3,
-	'database'
-),
-(
-	'https://shandan.one/guacamole',
-	'Guacamole',
-	'https://raw.githubusercontent.com/apache/guacamole-website/main/images/logos/guac-classic-logo.svg',
-	'Remote desktop',
-	'teal',
-	3,
-	'avocado'
-),
-(
-	'https://gogs.shandan.one',
-	'Gogs',
-	'https://gogs.shandan.one/img/favicon.png',
-	'Manage and access git repositories',
-	'yellow',
-	3,
-	'brand-git'
-),
-(
-	'https://syncthing.shandan.one',
-	'Syncthing',
-	'https://syncthing.net/img/logo-horizontal.svg',
-	'Monitor and configure file shares',
-	'blue',
-	3,
-	'topology-complex'
-),
-(
-	'https://shandan.one/wekan',
-	'WeKan',
-	'https://raw.githubusercontent.com/wekan/wekan/master/public/logo-header.png',
-	'Kanban board
-
-Manage projects and tasks',
-	'teal',
-	3,
-	'layout-kanban'
-),
-(
-	'https://mouse.shandan.one/#remote-mouse',
-	'Remote Touchpad',
-	'https://raw.githubusercontent.com/Unrud/remote-touchpad/master/webdata/icon.png',
-	'Use device as touchpad
-
-Turn your device''s touchscreen into a remote touchpad',
-	'github',
-	3,
-	'pointer'
-),
-(
-	'https://gateway.shandan.one',
-	'Gateway',
-	'https://gateway.shandan.one/favicon.ico?t=6dcc8b66',
-	'Access the gateway
-
-Manage local LAN and internet connection settings',
-	'cyan',
-	3,
-	'router'
-),
-(
-	'https://listgarden.shandan.one',
-	'ListGarden',
-	'https://www.wikicalc.org/images/listgardenlogo48.gif',
-	'Manage RSS Feeds
-
-Publish news items and create new feeds',
-	'green',
-	3,
-	'seeding'
-),
-(
-	'https://shandan.one/pyapi/random',
-	'Prayer Generator',
-	'',
-	'Generate a template prayer following the form of the model prayer outlined in Matthew&nbsp;6:9‑13 and Luke&nbsp;11:2‑4',
-	'orange',
-	3,
-	'pray'
-)
-;
-

+ 0 - 26
nginx.conf

@@ -1,26 +0,0 @@
-events {
-}
-http {
-  server {
-    root /usr/share/nginx/html;
-    location / {
-    }
-    location /feed/ {
-      autoindex on;
-    }
-    location /mail-archive/ {
-      autoindex on;
-    }
-    location /css/ {
-        types { } default_type "text/css; charset=utf-8";
-    }
-    location /media/ {
-    }
-    location /license/ {
-      charset UTF-8;
-      autoindex on;
-    }
-    location /.well-known/ {
-    }
-  }
-}

+ 0 - 7
rest/Dockerfile

@@ -1,7 +0,0 @@
-FROM python:3-slim
-WORKDIR /usr/src/app
-COPY rest/requirements.txt ./
-RUN python3 -m pip install --upgrade pip && \
-    python3 -m pip install --no-cache-dir -r requirements.txt
-COPY rest rest
-CMD [ "python", "-m", "rest.cherrypy" ]

+ 0 - 18
rest/__init__.py

@@ -1,18 +0,0 @@
-#
-# Copyright (c) Daniel Sheffield 2023
-#
-# All rights reserved
-#
-# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-from bottle import TEMPLATE_PATH
-
-TEMPLATE_PATH.append("rest/templates")
-ALL_UNITS = { 'g', 'kg', 'mL', 'L', 'Pieces', 'Bunches', 'Bags' }
-PARAMS = { 'group', 'category', 'product', 'unit', 'tag', 'organic' }
-BOOLEAN = {
-    "1": True,
-    True: "1",
-    "0": False,
-    False: "0",
-    None: "0.5",
-}

+ 0 - 14
rest/bar.py

@@ -1,14 +0,0 @@
-from barcode import EAN13, Code128, Code39
-
-BARCODE_MAP = {
-  'EAN_13': EAN13,
-  'CODE_128': Code128,
-  'CODE_39': Code39,
-}
-
-def get_bar_code(meta):
-    f = BARCODE_MAP.get(meta['format'], None)
-    if f is not None:
-        return f(meta['content']).render()
-
-    return None

+ 0 - 15
rest/cherrypy.py

@@ -1,15 +0,0 @@
-import cherrypy
-import bottle
-from .pyapi import *
-
-application = bottle.default_app()
-
-cherrypy.config.update({
-    'server.socket_host': "0.0.0.0",
-    'server.socket_port': 6772,
-})
-
-cherrypy.tree.graft(application, "/")
-
-cherrypy.engine.start()
-cherrypy.engine.block()

+ 0 - 1
rest/dev-requirements.txt

@@ -1 +0,0 @@
-pytest

+ 0 - 181
rest/hash_util.py

@@ -1,181 +0,0 @@
-from hashlib import blake2b, shake_128, md5, sha256, sha1
-import os
-from base64 import b64encode, b64decode, b85encode, b85decode
-import base32_lib as b32
-
-DIGEST_SIZE_BYTES = 3
-DIGEST_SIZE_BITMASK = 0xffffff
-DIGEST_SIZE_SIGNED_TO_UNSIGNED_BITMASK = 0x1ffffff
-DIGEST_SIZE_SIGNED_TO_UNSIGNED_BIT = 0x1000000
-DIGEST_SIZE_NIBBLES = DIGEST_SIZE_BYTES * 2
-B64ALTCHARS = b'.-'
-B32REGEX = r'[0-9a-tv-zA-TV-Z]'
-
-def sha1hash(data: str):
-    return sha1(data.encode("utf-8"), usedforsecurity=False).hexdigest()[:DIGEST_SIZE_BYTES]
-
-def sha256hash(data: str):
-    return sha256(data.encode("utf-8"), usedforsecurity=False).hexdigest()[:DIGEST_SIZE_BYTES]
-
-def md5hash(data: str):
-    return md5(data.encode("utf-8"), usedforsecurity=False).hexdigest()[:DIGEST_SIZE_BYTES]
-
-def shake(data: str):
-    return shake_128(data.encode("utf-8"), usedforsecurity=False).hexdigest(DIGEST_SIZE_BYTES)
-
-def blake(data: bytes, person: bytes = None) -> bytes:
-    return blake2b(
-        data,
-        usedforsecurity=False,
-        digest_size=DIGEST_SIZE_BYTES,
-        person=person
-    ).digest()
-
-def blake_file(path: str, person: bytes = None, root: str ='rest/static') -> bytes:
-    fd = os.open(f'{root}/{path}', os.O_RDONLY, 0o600)
-    with open(fd, "rb") as f:
-        f.seek(0)
-        _blake = blake2b(usedforsecurity=False, digest_size=DIGEST_SIZE_BYTES, person=person)
-        while f.peek(1):
-            _blake.update(f.read(1024))
-    return _blake.digest()
-        
-
-def python(data: str):
-    return hash(data)
-
-def normalize_hash(_hash: int) -> int:
-    #hex = hash_to_hex(_hash)
-    #return int(hex, 16)
-    #_bytes = _hash.to_bytes(8, byteorder='big', signed=True)
-    #return bytes_to_hash(_bytes)
-    return _hash & DIGEST_SIZE_BITMASK
-
-def normalize_bytes(_bytes: bytes) -> bytes:
-    return (b'\x00' * DIGEST_SIZE_BYTES + _bytes)[-DIGEST_SIZE_BYTES:]
-
-def normalize_hex(_hex: str) -> str:
-    #_bytes = hex_to_bytes(hex)
-    #return _bytes.hex()
-    return _hex.zfill(DIGEST_SIZE_NIBBLES)[-DIGEST_SIZE_NIBBLES:]
-
-def hex_to_bytes(_hex: str) -> bytes:
-    _bytes = bytes.fromhex(_hex.zfill(DIGEST_SIZE_NIBBLES))
-    return normalize_bytes(_bytes)
-
-def bytes_to_hex(_bytes: bytes) -> str:
-    return normalize_bytes(_bytes).hex()
-
-def hash_to_bytes(_hash: int) -> bytes:
-    _bytes = _hash.to_bytes(8, byteorder='big', signed=True)
-    return normalize_bytes(_bytes)
-
-def bytes_to_hash(_bytes: bytes) -> int:
-    norm = normalize_bytes(_bytes)
-    return int.from_bytes(norm, byteorder='big', signed=False)
-
-def hash_to_hex(_hash: int) -> str:
-    #return hash_to_bytes(_hash).hex()
-    #return normalize_hex(
-    #return f"{_hash + (1 << 64):x}"[-4:]
-    #return hex(_hash + (1<<64))[2:][-4:]
-    #return f"{_hash & 0xffff:04x}"
-    return hex((_hash|DIGEST_SIZE_SIGNED_TO_UNSIGNED_BIT) & DIGEST_SIZE_SIGNED_TO_UNSIGNED_BITMASK)[3:]
-
-def hex_to_hash(_hex: str) -> int:
-    #_bytes = bytes.fromhex(hex.zfill(4))
-    #return bytes_to_hash(_bytes)
-    #return int(normalize_hex(hex), 16)
-    return int(_hex, 16) & DIGEST_SIZE_BITMASK
-
-def remove_padding(f):
-    def wrap(*args, **kwargs):
-        return f(*args, **kwargs).split('=')[0]
-    return wrap
-
-def fix_padding(f):
-    def wrap(_b64, *args, **kwargs):
-        pad = (4 - len(_b64)) % 4
-        fixed = _b64 + '='*pad
-        return f(fixed, *args, **kwargs)
-    return wrap
-
-def normalize_base32(_b32: str):
-    return b32.encode(b32.decode(_b32)).upper().zfill(DIGEST_SIZE_BYTES*8//5+1)
-
-def add_padding_base32(f):
-    def wrap(*args, **kwargs):
-        return normalize_base32(f(*args, **kwargs))
-    return wrap
-
-@remove_padding
-def hash_to_base64(_hash: int) -> str:
-    return b64encode(hash_to_bytes(_hash), altchars=B64ALTCHARS).decode("utf-8")
-
-@remove_padding
-def hex_to_base64(_hex: str) -> str:
-    return b64encode(hex_to_bytes(_hex), altchars=B64ALTCHARS).decode("utf-8")
-
-@remove_padding
-def bytes_to_base64(_bytes: str) -> str:
-    return b64encode(normalize_bytes(_bytes), altchars=B64ALTCHARS).decode("utf-8")
-
-@fix_padding
-def base64_to_hash(_b64: str) -> str:
-    return bytes_to_hash(b64decode(_b64, altchars=B64ALTCHARS))
-
-@fix_padding
-def base64_to_hex(_b64: str) -> str:
-    return bytes_to_hex(b64decode(_b64, altchars=B64ALTCHARS))
-
-@fix_padding
-def base64_to_bytes(_b64: str) -> str:
-    return normalize_bytes(b64decode(_b64, altchars=B64ALTCHARS))
-
-#@remove_padding
-def hash_to_base85(_hash: int) -> str:
-    return b85encode(hash_to_bytes(_hash)).decode("utf-8")
-
-#@remove_padding
-def hex_to_base85(_hex: str) -> str:
-    return b85encode(hex_to_bytes(_hex)).decode("utf-8")
-
-#@remove_padding
-def bytes_to_base85(_bytes: str) -> str:
-    return b85encode(normalize_bytes(_bytes)).decode("utf-8")
-
-#@fix_padding
-def base85_to_hash(_b64: str) -> str:
-    return bytes_to_hash(b85decode(_b64))
-
-#@fix_padding
-def base85_to_hex(_b64: str) -> str:
-    return bytes_to_hex(b85decode(_b64))
-
-#@fix_padding
-def base85_to_bytes(_b64: str) -> str:
-    return normalize_bytes(b85decode(_b64))
-
-@add_padding_base32
-def hash_to_base32(_hash: int) -> str:
-    return b32.encode(_hash & DIGEST_SIZE_BITMASK)
-
-@add_padding_base32
-def hex_to_base32(_hex: str) -> str:
-    return b32.encode(hex_to_hash(_hex))
-
-@add_padding_base32
-def bytes_to_base32(_bytes: bytes) -> str:
-    return b32.encode(bytes_to_hash(_bytes))
-
-#@fix_padding
-def base32_to_hash(_b64: str) -> str:
-    return b32.decode(_b64)
-
-#@fix_padding
-def base32_to_hex(_b64: str) -> str:
-    return hash_to_hex(base32_to_hash(_b64))
-
-#@fix_padding
-def base32_to_bytes(_b64: str) -> str:
-    return hash_to_bytes(base32_to_hash(_b64))

+ 0 - 166
rest/pyapi.py

@@ -1,166 +0,0 @@
-#
-# Copyright (c) Daniel Sheffield 2024
-# All rights reserved
-#
-# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-
-import time
-from bottle import (
-    route, request, response,
-    redirect, abort, static_file,
-    HTTPResponse,
-)
-from itertools import chain
-from base64 import b64decode, b64encode
-from linkpreview import link_preview
-
-from .hash_util import B32REGEX, normalize_base32, blake, bytes_to_base32
-from .qr import get_qr_code
-from .bar import get_bar_code
-from json import dumps, load, loads
-from sqlite3 import connect
-from datetime import datetime, timezone
-
-SCHEME = "https://"
-HOST = ""
-DOMAIN = "shandan.one"
-PORT = ""
-LOCATION = SCHEME + (f"{HOST}." if HOST else "") + DOMAIN + (f":{PORT}" if PORT else "")
-
-def parse_data_uri(content):
-    # extract bytes from data url
-    _, *media, data = chain.from_iterable(map(
-        lambda x: x.split(',', 1), content.split(':', 1)
-    ))
-    media = media and media[0]
-    mimetype, *params, encoding = media.split(';')
-    if '=' in encoding:
-        params.append(encoding)
-        encoding = None
-    
-    return {
-        'mimetype': mimetype,
-        'params': dict(map(lambda x: x.split('='), params)),
-        'encoding': encoding,
-        'data': data,
-    }
-
-
-def parse_upload_placeholder(rowid):
-    rowid = int(rowid)
-    con = connect('util.db')
-    content = None
-    try:
-        content = con.cursor().execute("""
-SELECT content FROM upload_temp WHERE rowid = ? LIMIT 1;
-""", (rowid,)).fetchall()[0][0]
-    finally:
-        con.close()
-    
-    data = parse_data_uri(content)
-    assert data['encoding'] == 'base64', f"unsupported encoding: {data['encoding']}"
-    data = b64decode(data['data'] + '==')
-    return data
-
-
-@route('/<route:re:(clip|goto|upload|code)>/meta', method=['POST'])
-def get_meta(route):
-    response.content_type = 'application/json'
-    body = load(request.body)
-    person = route
-    if route == 'upload':
-        data = parse_upload_placeholder(body['data'])
-    elif route == 'code':
-        data = dumps(body['data'], sort_keys=True).encode('utf-8')
-    else:
-        data = body['data'].encode('utf-8')
-
-    _bytes = blake(data, person = person and person.encode('utf-8'))
-    hash = bytes_to_base32(_bytes)
-    fallback = f'https://shandan.one/{route}/{hash}'
-
-    preview = None
-    if route == 'goto':
-        link = data.decode('utf-8')
-        try:
-            page = link_preview(link, parser="lxml")
-            preview = {
-                'title': page.title,
-                'img': page.absolute_image,
-                'domain': page.site_name,
-                'link': link,
-            }
-        except:
-            pass
-    elif route == 'code':
-        if body['data']['format'] == 'QR_CODE':
-            preview = b64encode(get_qr_code(body['data']['content'], err_lvl=body['data']['errorCorrectionLevel']))
-        else:
-            preview = b64encode(get_bar_code(body['data']))
-        preview = preview.decode('utf-8')
-
-    qr = None
-    if route != 'code':
-        qr = get_qr_code(data, fallback = fallback).decode('utf-8')
-
-    return dumps({
-        'hash': hash,
-        'qr': qr,
-        'preview': preview,
-    })
-
-@route('/<route:re:(clip|goto|upload)>/normalize', method=['GET'])
-def normalize(route):
-    _hash = request.params.hash
-    response.content_type = 'application/json'
-    return dumps({
-        'i': _hash,
-        'o': normalize_base32(_hash) if _hash else None,
-    })
-
-@route('/static/<filename:path>')
-def send_static(filename):
-    return static_file(filename, root='rest/static')
-
-@route(f'/<route:re:(clip|goto|code)>/<hash:re:{B32REGEX}{{1,5}}>', method='GET')
-def get_clip(route, hash):
-    hash = hash and normalize_base32(hash)
-    return redirect(f'/{route}/?hash={hash}&go=true')
-
-
-@route(f'/upload/<hash:re:{B32REGEX}{{1,5}}>', method='GET')
-def get_upload(hash):
-    hash = hash and normalize_base32(hash)
-    con = connect('util.db')
-    fname, mimetype, content = (None, None, None)
-    try:
-        fname, mimetype, content, created = con.cursor().execute("""
-SELECT name, mime, content, created FROM upload WHERE hash = ? LIMIT 1;
-""", (hash,)).fetchall()[0]
-    finally:
-        con.close()
-
-    created = datetime.strptime(created, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc).timestamp()
-    data = parse_data_uri(content)
-    assert data['mimetype'].split(';', 1)[0] == mimetype.split(';', 1)[0].split('+')[0], f"mimetype in db and data uri differ"
-    charset = data['params'].get('charset', None)
-    assert data['encoding'] == 'base64', f"unsupported encoding: {data['encoding']}"
-    content = b64decode(data['data'] + '==')
-
-    headers = {}
-    headers['Content-Length'] = len(content)
-
-    # TODO: create ext from mime type?
-    headers['Content-Disposition'] = 'attachment; filename="%s"' % (fname or hash)
-    headers['Content-Encoding'] = 'application/octet-stream'
-
-    if mimetype:
-        if mimetype[:5] == 'text/' and charset and 'charset' not in mimetype:
-            mimetype += '; charset=%s' % charset
-        headers['Content-Type'] = mimetype
-    lm = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(created))
-    headers['Last-Modified'] = lm
-    return HTTPResponse(content, **headers)
-
-@route('/<any>/', method='GET')
-def redirect_trailing_slash(any): return redirect(f'/{any}')

+ 0 - 36
rest/qr.py

@@ -1,36 +0,0 @@
-from io import BytesIO
-from typing import Union
-from qrcode import QRCode
-from qrcode.constants import ERROR_CORRECT_L, ERROR_CORRECT_M, ERROR_CORRECT_Q, ERROR_CORRECT_H
-from qrcode.image.svg import SvgPathFillImage
-
-QR_MAX_BYTES = {
-    ERROR_CORRECT_H: 1273,
-    ERROR_CORRECT_Q: 1663,
-    ERROR_CORRECT_M: 2331,
-    ERROR_CORRECT_L: 2953,
-}
-QR_QUALITY_MAP = {
-  q: locals().get(f'ERROR_CORRECT_{q}') for q in (
-    'H', 'Q', 'M', 'L'
-  )
-}
-def get_qr_code(data: Union[bytes, str], fallback: Union[bytes, str] = None, err_lvl='H'):
-    err_lvl = QR_QUALITY_MAP.get(err_lvl, err_lvl)
-    qr = QRCode(error_correction=err_lvl)
-    if data is not None and isinstance(data, str):
-        data = data.encode('utf-8')
-    if fallback is not None and isinstance(fallback, str):
-        fallback = fallback.encode('utf-8')
-    if fallback is not None and data and len(data) > QR_MAX_BYTES[err_lvl]:
-        qr.add_data(fallback, optimize=0)
-    else:
-        qr.add_data(data or fallback, optimize=0)
-
-    img_1 = qr.make_image(image_factory=SvgPathFillImage)
-    with BytesIO() as f:
-        img_1.save(f)
-        f.flush()
-        ret = f.getvalue()
-    return ret
-    

+ 0 - 7
rest/requirements.txt

@@ -1,7 +0,0 @@
-bottle
-cherrypy
-base32-lib
-lxml
-linkpreview
-qrcode
-python-barcode

BIN
rest/static/clip/144.png


BIN
rest/static/clip/192.png


BIN
rest/static/clip/48.png


BIN
rest/static/clip/512.png


BIN
rest/static/clip/72.png


BIN
rest/static/clip/96.png


+ 0 - 9
rest/static/clip/clip-favicon.svg

@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  Source: https://commons.wikimedia.org/wiki/File:Clipboard_(1714)_-_The_Noun_Project.svg
-  Author: Seth Taylor, CC0, via Wikimedia Commons
-  License: https://creativecommons.org/publicdomain/zero/1.0/deed.en
--->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 71 100" enable-background="new 0 0 71 100" xml:space="preserve">
-<rect fill="#fff" y="10" x="15" width="60" height="85"/>
-<path d="M70,7.583H56.625V5.961c0-0.55-0.45-1-1-1H43.443V2c0-1.1-0.9-2-2-2H29.557c-1.1,0-2,0.9-2,2v2.961H15.375  c-0.55,0-1,0.45-1,1v1.623H1c-0.55,0-1,0.45-1,1V99c0,0.55,0.45,1,1,1h69c0.55,0,1-0.45,1-1V8.583C71,8.034,70.55,7.583,70,7.583z   M35.5,2.319c1.094,0,1.981,0.887,1.981,1.98c0,1.094-0.888,1.981-1.981,1.981c-1.094,0-1.981-0.887-1.981-1.981  C33.519,3.206,34.406,2.319,35.5,2.319z M63.495,92.395c0,0.55-0.45,1-1,1H8.505c-0.55,0-1-0.45-1-1V15.188c0-0.55,0.45-1,1-1h5.87  v1.669c0,0.55,0.45,1,1,1h40.25c0.55,0,1-0.45,1-1v-1.669h5.87c0.55,0,1,0.45,1,1V92.395z"></path><rect x="14.375" y="25.5" width="38.125" height="2.375"></rect><rect x="14.375" y="31.625" width="38.125" height="2.375"></rect><rect x="14.375" y="37.75" width="29.125" height="2.375"></rect><rect x="14.375" y="50" width="38.125" height="2.375"></rect><rect x="14.375" y="56.125" width="38.125" height="2.375"></rect><rect x="14.375" y="62.25" width="29.125" height="2.375"></rect></svg>

+ 0 - 93
rest/static/clip/clip-favicon_square.svg

@@ -1,93 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  Source: https://commons.wikimedia.org/wiki/File:Clipboard_(1714)_-_The_Noun_Project.svg
-  Author: Seth Taylor, CC0, via Wikimedia Commons
-  License: https://creativecommons.org/publicdomain/zero/1.0/deed.en
--->
-
-<svg
-   version="1.1"
-   x="0px"
-   y="0px"
-   viewBox="0 0 100 100"
-   enable-background="new 0 0 71 100"
-   xml:space="preserve"
-   id="svg68"
-   sodipodi:docname="clip-favicon_square.svg"
-   width="100"
-   height="100"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   inkscape:export-filename="/home/das/git/home-launcher/rest/static/clip-favicon_512.png"
-   inkscape:export-xdpi="491.51999"
-   inkscape:export-ydpi="491.51999"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"><defs
-   id="defs72" /><sodipodi:namedview
-   id="namedview70"
-   pagecolor="#ffffff"
-   bordercolor="#666666"
-   borderopacity="1.0"
-   inkscape:pageshadow="2"
-   inkscape:pageopacity="0.0"
-   inkscape:pagecheckerboard="0"
-   showgrid="false"
-   width="100px"
-   inkscape:zoom="5.94"
-   inkscape:cx="23.063973"
-   inkscape:cy="49.915825"
-   inkscape:window-width="1680"
-   inkscape:window-height="997"
-   inkscape:window-x="0"
-   inkscape:window-y="0"
-   inkscape:window-maximized="1"
-   inkscape:current-layer="svg68" />
-<rect
-   fill="#ffffff"
-   opacity="0"
-   y="10"
-   x="20"
-   width="60"
-   height="85"
-   id="rect4" />
-<path
-   fill="#2fb344"
-   d="M 84.5,7.583 H 71.125 V 5.961 c 0,-0.55 -0.45,-1 -1,-1 H 57.943 V 2 c 0,-1.1 -0.9,-2 -2,-2 H 44.057 c -1.1,0 -2,0.9 -2,2 V 4.961 H 29.875 c -0.55,0 -1,0.45 -1,1 V 7.584 H 15.5 c -0.55,0 -1,0.45 -1,1 V 99 c 0,0.55 0.45,1 1,1 h 69 c 0.55,0 1,-0.45 1,-1 V 8.583 c 0,-0.549 -0.45,-1 -1,-1 z M 50,2.319 c 1.094,0 1.981,0.887 1.981,1.98 0,1.094 -0.888,1.981 -1.981,1.981 -1.094,0 -1.981,-0.887 -1.981,-1.981 0,-1.093 0.887,-1.98 1.981,-1.98 z m 27.995,90.076 c 0,0.55 -0.45,1 -1,1 h -53.99 c -0.55,0 -1,-0.45 -1,-1 V 15.188 c 0,-0.55 0.45,-1 1,-1 h 5.87 v 1.669 c 0,0.55 0.45,1 1,1 h 40.25 c 0.55,0 1,-0.45 1,-1 v -1.669 h 5.87 c 0.55,0 1,0.45 1,1 z"
-   id="path54" /><rect
-   fill="#2fb344"
-   x="28.875"
-   y="25.5"
-   width="38.125"
-   height="2.375"
-   id="rect56" /><rect
-   fill="#2fb344"
-   x="28.875"
-   y="31.625"
-   width="38.125"
-   height="2.375"
-   id="rect58" /><rect
-   fill="#2fb344"
-   x="28.875"
-   y="37.75"
-   width="29.125"
-   height="2.375"
-   id="rect60" /><rect
-   fill="#2fb344"
-   x="28.875"
-   y="50"
-   width="38.125"
-   height="2.375"
-   id="rect62" /><rect
-   fill="#2fb344"
-   x="28.875"
-   y="56.125"
-   width="38.125"
-   height="2.375"
-   id="rect64" /><rect
-   fill="#2fb344"
-   x="28.875"
-   y="62.25"
-   width="29.125"
-   height="2.375"
-   id="rect66" /></svg>

+ 0 - 61
rest/static/clip/manifest.json

@@ -1,61 +0,0 @@
-{
-  "id": "/clip",
-  "name": "Paste & Clip",
-  "short_name": "Clip",
-  "description": "Save snippets of text to a virtual clipboard to share via a short URL",
-  "start_url": "/clip/",
-  "shortcuts": [
-    {
-      "name": "Open",
-      "url": "/clip?action=open",
-      "icons": [{ "src": "/static/ftark-open.png", "sizes": "96x96" }]
-    },
-    {
-      "name": "New",
-      "url": "/clip",
-      "icons": [{ "src": "/static/clip/96.png", "sizes": "96x96" }]
-    }
-  ],
-  "scope": "/clip/",
-  "theme_color": "#4f8f4f",
-  "background_color": "black",
-  "display": "standalone",
-  "icons": [
-    {
-      "src": "/static/clip/512.png",
-      "sizes": "512x512",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/clip/192.png",
-      "sizes": "192x192",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/clip/144.png",
-      "sizes": "144x144",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/clip/96.png",
-      "sizes": "96x96",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/clip/72.png",
-      "sizes": "72x72",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/clip/48.png",
-      "sizes": "48x48",
-      "type": "image/png",
-      "purpose": "any"
-    }
-  ]
-}

File diff suppressed because it is too large
+ 0 - 1
rest/static/clip/qr.svg


File diff suppressed because it is too large
+ 0 - 1
rest/static/code/qr.svg


BIN
rest/static/ftark-open.png


+ 0 - 138
rest/static/ftark-open.svg

@@ -1,138 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<!-- 
-  Source: https://publicdomainvectors.org/en/free-clipart/Open-folder-monochrome-icon/87757.html
-  License: Public Domain
--->
-
-<svg
-   id="svg1"
-   sodipodi:docname="ftark-open.svg"
-   viewBox="0 0 60 60"
-   sodipodi:version="0.32"
-   version="1.0"
-   y="0"
-   x="0"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   width="60"
-   height="60"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:dc="http://purl.org/dc/elements/1.1/">
-  <defs
-     id="defs8" />
-  <sodipodi:namedview
-     id="base"
-     bordercolor="#666666"
-     inkscape:pageshadow="2"
-     inkscape:window-width="1680"
-     pagecolor="#ffffff"
-     inkscape:zoom="6.9465337"
-     inkscape:window-x="0"
-     borderopacity="1.0"
-     inkscape:cx="37.716653"
-     inkscape:cy="31.74245"
-     inkscape:window-y="0"
-     inkscape:window-height="997"
-     inkscape:pageopacity="0.0"
-     inkscape:pagecheckerboard="0"
-     showgrid="false"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="svg1"
-     fit-margin-top="0"
-     fit-margin-left="0"
-     fit-margin-right="0"
-     fit-margin-bottom="0" />
-  <rect
-     style="fill:none;stroke-width:4.27435"
-     id="rect845"
-     width="60"
-     height="60"
-     x="0"
-     y="0"
-     rx="0"
-     ry="0"
-     inkscape:export-filename="/home/das/git/home-launcher/rest/static/ftark-open.png"
-     inkscape:export-xdpi="153.60001"
-     inkscape:export-ydpi="153.60001" />
-  <g
-     id="g929"
-     transform="translate(-0.60199608,-0.73891072)"
-     inkscape:export-filename="/home/das/git/home-launcher/rest/static/ftark-open.png"
-     inkscape:export-xdpi="153.60001"
-     inkscape:export-ydpi="153.60001">
-    <path
-       id="path938"
-       style="fill:#b3b3b3;fill-rule:evenodd;stroke:#333333;stroke-width:3.125;stroke-linejoin:round"
-       d="m 5.433,8.4867 c -0.5262,0 -0.9687,0.4425 -0.9687,0.9687 V 14.33 C 4.4631,14.355 4.433,14.368 4.433,14.393 v 37 c 0,0.884 0.71,1.594 1.5938,1.594 h 37.281 c 0.884,0 1.594,-0.71 1.594,-1.594 v -37 c 0,-0.884 -0.71,-1.594 -1.594,-1.594 H 28.5888 V 9.4554 c 0,-0.5262 -0.442,-0.9687 -0.968,-0.9687 H 5.4328 Z" />
-    <rect
-       id="rect922"
-       style="fill:#7f7f7f;fill-rule:evenodd;stroke:#333333;stroke-width:3.2351;stroke-linejoin:round"
-       rx="1.5953"
-       transform="matrix(1,0,-0.35965,0.93309,0,0)"
-       width="40.476002"
-       sodipodi:stroke-cmyk="(0 0 0 0.8)"
-       y="23.398001"
-       x="25.032"
-       height="33.450001"
-       sodipodi:fill-cmyk="(0 0 0 0.5)" />
-  </g>
-  <metadata
-     id="metadata5">
-    <rdf:RDF>
-      <cc:Work>
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <cc:license
-           rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
-        <dc:publisher>
-          <cc:Agent
-             rdf:about="http://openclipart.org/">
-            <dc:title>Openclipart</dc:title>
-          </cc:Agent>
-        </dc:publisher>
-        <dc:title>ftark open</dc:title>
-        <dc:date>2011-01-31T02:01:08</dc:date>
-        <dc:description>Originally uploaded by Danny Allen for OCAL 0.18 this icon is part of the flat theme</dc:description>
-        <dc:source>https://openclipart.org/detail/111721/ftark-open-by-anonymous</dc:source>
-        <dc:creator>
-          <cc:Agent>
-            <dc:title>Anonymous</dc:title>
-          </cc:Agent>
-        </dc:creator>
-        <dc:subject>
-          <rdf:Bag>
-            <rdf:li>flat</rdf:li>
-            <rdf:li>icon</rdf:li>
-            <rdf:li>theme</rdf:li>
-          </rdf:Bag>
-        </dc:subject>
-      </cc:Work>
-      <cc:License
-         rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
-        <cc:permits
-           rdf:resource="http://creativecommons.org/ns#Reproduction" />
-        <cc:permits
-           rdf:resource="http://creativecommons.org/ns#Distribution" />
-        <cc:permits
-           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
-      </cc:License>
-    </rdf:RDF>
-  </metadata>
-  <rect
-     style="fill:#8f4f4f;stroke-width:4"
-     id="rect1382"
-     width="0.14395669"
-     height="1.1516535"
-     x="29.928022"
-     y="29.424173"
-     inkscape:export-filename="/home/das/git/home-launcher/rest/static/ftark-open.png"
-     inkscape:export-xdpi="153.60001"
-     inkscape:export-ydpi="153.60001" />
-</svg>

BIN
rest/static/goto/144.png


BIN
rest/static/goto/192.png


BIN
rest/static/goto/48.png


BIN
rest/static/goto/512.png


BIN
rest/static/goto/72.png


BIN
rest/static/goto/96.png


File diff suppressed because it is too large
+ 0 - 69
rest/static/goto/chain-link-3-2.svg


File diff suppressed because it is too large
+ 0 - 49
rest/static/goto/chain-link-cut-away.svg


File diff suppressed because it is too large
+ 0 - 50
rest/static/goto/chain-link.svg


File diff suppressed because it is too large
+ 0 - 62
rest/static/goto/chain-link2-3-2.svg


File diff suppressed because it is too large
+ 0 - 51
rest/static/goto/chain-link2-cut-away.svg


File diff suppressed because it is too large
+ 0 - 51
rest/static/goto/chain-link2.svg


File diff suppressed because it is too large
+ 0 - 76
rest/static/goto/chain-link2fr-3-2.svg


File diff suppressed because it is too large
+ 0 - 75
rest/static/goto/chain-link2r-3-2.svg


+ 0 - 61
rest/static/goto/manifest.json

@@ -1,61 +0,0 @@
-{
-  "id": "/goto",
-  "name": "GoTo... Tiny URL",
-  "short_name": "GoTo",
-  "description": "Share links by generating a short URL that can be easily typed or verbalised",
-  "start_url": "/goto",
-  "shortcuts": [
-    {
-      "name": "Open",
-      "url": "/goto?action=open",
-      "icons": [{ "src": "/static/ftark-open.png", "sizes": "96x96" }]
-    },
-    {
-      "name": "New",
-      "url": "/goto",
-      "icons": [{ "src": "/static/goto/96.png", "sizes": "96x96" }]
-    }
-  ],
-  "scope": "/goto",
-  "theme_color": "#8f4f4f",
-  "background_color": "black",
-  "display": "standalone",
-  "icons": [
-    {
-      "src": "/static/goto/512.png",
-      "sizes": "512x512",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/goto/192.png",
-      "sizes": "192x192",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/goto/144.png",
-      "sizes": "144x144",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/goto/96.png",
-      "sizes": "96x96",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/goto/72.png",
-      "sizes": "72x72",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/goto/48.png",
-      "sizes": "48x48",
-      "type": "image/png",
-      "purpose": "any"
-    }
-  ]
-}

File diff suppressed because it is too large
+ 0 - 1
rest/static/goto/qr.svg


BIN
rest/static/shandanone-small.png


+ 0 - 68
rest/static/shandanone-small.svg

@@ -1,68 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   width="104.20946mm"
-   height="95.790558mm"
-   viewBox="0 0 104.20946 95.790558"
-   version="1.1"
-   id="svg5"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   sodipodi:docname="shandanone-small.svg"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg">
-  <sodipodi:namedview
-     id="namedview7"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageshadow="2"
-     inkscape:pageopacity="0.0"
-     inkscape:pagecheckerboard="true"
-     inkscape:document-units="mm"
-     showgrid="false"
-     fit-margin-top="0"
-     fit-margin-left="0"
-     fit-margin-right="0"
-     fit-margin-bottom="0"
-     inkscape:zoom="0.56569164"
-     inkscape:cx="61.871164"
-     inkscape:cy="181.19412"
-     inkscape:window-width="1680"
-     inkscape:window-height="997"
-     inkscape:window-x="1920"
-     inkscape:window-y="0"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="layer1" />
-  <defs
-     id="defs2" />
-  <g
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer"
-     id="layer1"
-     transform="translate(-52.895271,-100.60472)">
-    <rect
-       style="fill:#00ffff;stroke-width:1.58288"
-       id="rect9235"
-       width="104.20946"
-       height="95.790558"
-       x="52.895271"
-       y="100.60472"
-       rx="9.1908665"
-       ry="8.2469568" />
-    <text
-       xml:space="preserve"
-       style="font-style:normal;font-weight:normal;font-size:82.0971px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.05243"
-       x="45.846691"
-       y="180.05571"
-       id="text1948"
-       transform="scale(1.040286,0.96127411)"><tspan
-         sodipodi:role="line"
-         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:FreeMono;-inkscape-font-specification:'FreeMono Bold';stroke-width:2.05243"
-         x="45.846691"
-         y="180.05571"
-         id="tspan31781">.1</tspan></text>
-  </g>
-</svg>

BIN
rest/static/shandanone2.png


+ 0 - 78
rest/static/shandanone2.svg

@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   width="104.20946mm"
-   height="95.790558mm"
-   viewBox="0 0 104.20946 95.790558"
-   version="1.1"
-   id="svg5"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   sodipodi:docname="shandanone2.svg"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg">
-  <sodipodi:namedview
-     id="namedview7"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageshadow="2"
-     inkscape:pageopacity="0.0"
-     inkscape:pagecheckerboard="true"
-     inkscape:document-units="mm"
-     showgrid="false"
-     fit-margin-top="0"
-     fit-margin-left="0"
-     fit-margin-right="0"
-     fit-margin-bottom="0"
-     inkscape:zoom="0.56569164"
-     inkscape:cx="61.871164"
-     inkscape:cy="181.19412"
-     inkscape:window-width="1680"
-     inkscape:window-height="997"
-     inkscape:window-x="1920"
-     inkscape:window-y="0"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="layer1" />
-  <defs
-     id="defs2" />
-  <g
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer"
-     id="layer1"
-     transform="translate(-52.895271,-100.60472)">
-    <rect
-       style="fill:#00ffff;stroke-width:1.58288"
-       id="rect9235"
-       width="104.20946"
-       height="95.790558"
-       x="52.895271"
-       y="100.60472"
-       rx="9.1908665"
-       ry="8.2469568" />
-    <text
-       xml:space="preserve"
-       style="font-style:normal;font-weight:normal;font-size:23.4617px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.586543"
-       x="66.327843"
-       y="131.03873"
-       id="text1948"
-       transform="scale(1.040286,0.96127411)"><tspan
-         sodipodi:role="line"
-         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:FreeMono;-inkscape-font-specification:'FreeMono Bold';stroke-width:0.586543"
-         x="66.327843"
-         y="131.03873"
-         id="tspan4071">SHAN</tspan><tspan
-         sodipodi:role="line"
-         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:FreeMono;-inkscape-font-specification:'FreeMono Bold';stroke-width:0.586543"
-         x="66.327843"
-         y="161.30981"
-         id="tspan6473"> DAN</tspan><tspan
-         sodipodi:role="line"
-         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:FreeMono;-inkscape-font-specification:'FreeMono Bold';stroke-width:0.586543"
-         x="66.327843"
-         y="191.58092"
-         id="tspan6475"> .ONE</tspan></text>
-  </g>
-</svg>

BIN
rest/static/upload/144.png


BIN
rest/static/upload/192.png


BIN
rest/static/upload/48.png


BIN
rest/static/upload/512.png


BIN
rest/static/upload/72.png


BIN
rest/static/upload/96.png


+ 0 - 61
rest/static/upload/manifest.json

@@ -1,61 +0,0 @@
-{
-  "id": "/upload",
-  "name": "Upload Files",
-  "short_name": "Upload",
-  "description": "Upload a file for sharing via a short URL",
-  "start_url": "/upload",
-  "shortcuts": [
-    {
-      "name": "Open",
-      "url": "/upload?action=open",
-      "icons": [{ "src": "/static/ftark-open.png", "sizes": "96x96" }]
-    },
-    {
-      "name": "New",
-      "url": "/upload",
-      "icons": [{ "src": "/static/upload/96.png", "sizes": "96x96" }]
-    }
-  ],
-  "scope": "/upload",
-  "theme_color": "#afaf0f",
-  "background_color": "black",
-  "display": "standalone",
-  "icons": [
-    {
-      "src": "/static/upload/512.png",
-      "sizes": "512x512",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/upload/192.png",
-      "sizes": "192x192",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/upload/144.png",
-      "sizes": "144x144",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/upload/96.png",
-      "sizes": "96x96",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/upload/72.png",
-      "sizes": "72x72",
-      "type": "image/png",
-      "purpose": "any"
-    },
-    {
-      "src": "/static/upload/48.png",
-      "sizes": "48x48",
-      "type": "image/png",
-      "purpose": "any"
-    }
-  ]
-}

File diff suppressed because it is too large
+ 0 - 1
rest/static/upload/qr.svg


+ 0 - 46
rest/static/upload/upload-favicon.svg

@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  Source: https://commons.wikimedia.org/wiki/File:Upload_(89524)_-_The_Noun_Project.svg
-  Author: Consumer Financial Protection Bureau, CC0, via Wikimedia Commons
-  License: https://creativecommons.org/publicdomain/zero/1.0/deed.en
--->
-
-<svg
-   version="1.1"
-   x="0px"
-   y="0px"
-   viewBox="0 0 90 100"
-   enable-background="new 0 0 100 100"
-   xml:space="preserve"
-   id="svg51"
-   sodipodi:docname="upload-favicon.svg"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   width="90"
-   height="100"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"><defs
-     id="defs55" /><sodipodi:namedview
-     id="namedview53"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageshadow="2"
-     inkscape:pageopacity="0.0"
-     inkscape:pagecheckerboard="0"
-     showgrid="false"
-     inkscape:zoom="7.65"
-     inkscape:cx="16.013072"
-     inkscape:cy="50"
-     inkscape:window-width="1680"
-     inkscape:window-height="997"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="svg51" />
-<rect fill="#fff" y="10" x="10" width="42" height="23"/>
-<rect fill="#fff" y="31" x="10" width="60" height="58"/>
-<path
-     d="m 47.8,34.8 c -1.5,-1.6 -4.1,-1.6 -5.6,0 0,0 -12.1,12.1 -12.5,12.5 -1.6,1.6 -1.6,4.1 0,5.7 1.5,1.6 4.1,1.6 5.7,0 0.4,-0.4 5.6,-5.6 5.6,-5.6 V 76 c 0,2.2 1.9,4.1 4,4.1 2.2,0 4,-1.9 4,-4.1 V 47.4 c 0,0 5.5,5.4 5.6,5.6 1.6,1.6 4.1,1.6 5.8,0 1.5,-1.6 1.5,-4.1 0,-5.7 C 60.3,47.1 47.8,34.8 47.8,34.8 Z m 31.5,-9.2 c 0,0 -0.4,-0.4 -19.4,-19.4 C 59.4,5.6 58.3,5 57,5 H 13.6 C 11.3,5 9.5,6.8 9.5,9.1 v 81.8 c 0,2.3 1.8,4.1 4.1,4.1 h 62.8 c 2.3,0 4,-1.8 4,-4.1 V 28.5 C 80.5,27.2 80,26.2 79.3,25.6 Z M 17.7,13.2 H 52.9 L 53,28.5 c 0,2.2 1.8,4 4,4 H 72.3 V 86.8 H 17.7 Z"
-     id="path49" /></svg>

+ 0 - 63
rest/static/upload/upload-favicon_maskable.svg

@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  Source: https://commons.wikimedia.org/wiki/File:Upload_(89524)_-_The_Noun_Project.svg
-  Author: Consumer Financial Protection Bureau, CC0, via Wikimedia Commons
-  License: https://creativecommons.org/publicdomain/zero/1.0/deed.en
--->
-
-<svg
-   version="1.1"
-   x="0px"
-   y="0px"
-   viewBox="0 0 145 145"
-   enable-background="new 0 0 100 100"
-   xml:space="preserve"
-   id="svg51"
-   sodipodi:docname="upload-favicon_maskable.svg"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   width="145"
-   height="145"
-   inkscape:export-filename="/home/das/git/home-launcher/rest/static/upload-favicon_maskable_72.png"
-   inkscape:export-xdpi="47.668964"
-   inkscape:export-ydpi="47.668964"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"><defs
-   id="defs55" /><sodipodi:namedview
-   id="namedview53"
-   pagecolor="#ffffff"
-   bordercolor="#666666"
-   borderopacity="1.0"
-   inkscape:pageshadow="2"
-   inkscape:pageopacity="1"
-   inkscape:pagecheckerboard="0"
-   showgrid="false"
-   inkscape:zoom="5.4642857"
-   inkscape:cx="69.908497"
-   inkscape:cy="70.366013"
-   inkscape:window-width="1680"
-   inkscape:window-height="997"
-   inkscape:window-x="0"
-   inkscape:window-y="0"
-   inkscape:window-maximized="1"
-   inkscape:current-layer="svg51"
-   width="100px"
-   units="px" />
-<rect
-   fill="#ffffff"
-   y="32.5"
-   x="42.5"
-   width="42"
-   height="23"
-   id="rect169" />
-<rect
-   fill="#ffffff"
-   y="53.5"
-   x="42.5"
-   width="60"
-   height="58"
-   id="rect171" />
-<path
-   d="m 75.3,57.3 c -1.5,-1.6 -4.1,-1.6 -5.6,0 0,0 -12.1,12.1 -12.5,12.5 -1.6,1.6 -1.6,4.1 0,5.7 1.5,1.6 4.1,1.6 5.7,0 0.4,-0.4 5.6,-5.6 5.6,-5.6 v 28.6 c 0,2.2 1.9,4.1 4,4.1 2.2,0 4,-1.9 4,-4.1 V 69.9 c 0,0 5.5,5.4 5.6,5.6 1.6,1.6 4.1,1.6 5.8,0 1.5,-1.6 1.5,-4.1 0,-5.7 C 87.8,69.6 75.3,57.3 75.3,57.3 Z m 31.5,-9.2 c 0,0 -0.4,-0.4 -19.4,-19.4 -0.5,-0.6 -1.6,-1.2 -2.9,-1.2 H 41.1 c -2.3,0 -4.1,1.8 -4.1,4.1 v 81.8 c 0,2.3 1.8,4.1 4.1,4.1 h 62.8 c 2.3,0 4,-1.8 4,-4.1 V 51 c 0.1,-1.3 -0.4,-2.3 -1.1,-2.9 z M 45.2,35.7 H 80.4 L 80.5,51 c 0,2.2 1.8,4 4,4 h 15.3 v 54.3 H 45.2 Z"
-   id="path49" /></svg>

+ 0 - 65
rest/static/upload/upload-favicon_square.svg

@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!--
-  Source: https://commons.wikimedia.org/wiki/File:Upload_(89524)_-_The_Noun_Project.svg
-  Author: Consumer Financial Protection Bureau, CC0, via Wikimedia Commons
-  License: https://creativecommons.org/publicdomain/zero/1.0/deed.en
--->
-
-<svg
-   version="1.1"
-   x="0px"
-   y="0px"
-   viewBox="0 0 100 100"
-   enable-background="new 0 0 100 100"
-   xml:space="preserve"
-   id="svg51"
-   sodipodi:docname="upload-favicon_square.svg"
-   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
-   width="100"
-   height="100"
-   inkscape:export-filename="/home/das/git/home-launcher/rest/static/upload-favicon_512.png"
-   inkscape:export-xdpi="491.51999"
-   inkscape:export-ydpi="491.51999"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"><defs
-   id="defs55" /><sodipodi:namedview
-   id="namedview53"
-   pagecolor="#ffffff"
-   bordercolor="#666666"
-   borderopacity="1.0"
-   inkscape:pageshadow="2"
-   inkscape:pageopacity="1"
-   inkscape:pagecheckerboard="0"
-   showgrid="false"
-   inkscape:zoom="7.65"
-   inkscape:cx="49.150327"
-   inkscape:cy="50"
-   inkscape:window-width="1680"
-   inkscape:window-height="997"
-   inkscape:window-x="0"
-   inkscape:window-y="0"
-   inkscape:window-maximized="1"
-   inkscape:current-layer="svg51"
-   width="100px" />
-<rect
-   fill="#ffffff"
-   opacity="0"
-   y="10"
-   x="20"
-   width="42"
-   height="23"
-   id="rect603" />
-<rect
-   fill="#ffffff"
-   opacity="0"
-   y="31"
-   x="20"
-   width="60"
-   height="58"
-   id="rect605" />
-<path
-   fill="#f59f00"
-   d="m 52.8,34.8 c -1.5,-1.6 -4.1,-1.6 -5.6,0 0,0 -12.1,12.1 -12.5,12.5 -1.6,1.6 -1.6,4.1 0,5.7 1.5,1.6 4.1,1.6 5.7,0 0.4,-0.4 5.6,-5.6 5.6,-5.6 V 76 c 0,2.2 1.9,4.1 4,4.1 2.2,0 4,-1.9 4,-4.1 V 47.4 c 0,0 5.5,5.4 5.6,5.6 1.6,1.6 4.1,1.6 5.8,0 1.5,-1.6 1.5,-4.1 0,-5.7 C 65.3,47.1 52.8,34.8 52.8,34.8 Z m 31.5,-9.2 c 0,0 -0.4,-0.4 -19.4,-19.4 C 64.4,5.6 63.3,5 62,5 H 18.6 c -2.3,0 -4.1,1.8 -4.1,4.1 v 81.8 c 0,2.3 1.8,4.1 4.1,4.1 h 62.8 c 2.3,0 4,-1.8 4,-4.1 V 28.5 C 85.5,27.2 85,26.2 84.3,25.6 Z M 22.7,13.2 H 57.9 L 58,28.5 c 0,2.2 1.8,4 4,4 H 77.3 V 86.8 H 22.7 Z"
-   id="path49" /></svg>

+ 0 - 5
rest/tool_color.py

@@ -1,5 +0,0 @@
-color = {
-    'clip': '#4f8f4f',
-    'goto': '#8f4f4f',
-    'upload': '#afaf0f',
-}

+ 0 - 146
rest/validate.py

@@ -1,146 +0,0 @@
-#
-# Copyright (c) Daniel Sheffield 2024
-# All rights reserved
-#
-# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-
-# https://www.ietf.org/rfc/rfc3696.txt
-
-"""
-   Without quotes, local-parts may consist of any combination of
-   alphabetic characters, digits, or any of the special characters
-
-      ! # $ % & ' * + - / = ?  ^ _ ` . { | } ~
-
-   period (".") may also appear, but may not be used to start or end the
-   local part, nor may two or more consecutive periods appear.  Stated
-   differently, any ASCII graphic (printing) character other than the
-   at-sign ("@"), backslash, double quote, comma, or square brackets may
-   appear without quoting.  If any of that list of excluded characters
-   are to appear, they must be quoted.
-"""
-
-from io import BufferedReader
-import mimetypes
-from itertools import chain
-import os
-from bottle import static_file, HTTPError, abort, LocalRequest, HTTPResponse
-from urllib.parse import urlparse, quote, quote_plus
-from .hash_util import blake_file, bytes_to_base32, blake
-
-# according to rfc3696
-URL_MUST_ESCAPE = bytes([
-    x for x in chain(
-        # control characters
-        range(int('0x1F', 0)+1),
-        # 0x7F and non 7bit-ASCII
-        range(int('0x7F', 0,), int('0xFF', 0)+1),
-        # specifically excluded
-        b'@\\",[]'
-    )
-])
-# so give this list to urllib.parse.quote which follows rfc3986
-URL_SAFE = bytes(( i for i in range(int('0xff',0)+1) if i not in map(int, URL_MUST_ESCAPE) ))
-
-CLIP_SIZE_LIMIT = 65535
-
-def get_filename(filename: str, root: str = 'rest/static/files'):
-    path = '/'.join([filename,]*2)
-    try:
-        with open(f'{root}/{path}.name', "r") as f:
-            name = f.read()
-        return name
-    except:
-        pass
-
-def get_file_size(filename: str, root: str = 'rest/static/files'):
-    path = '/'.join([filename,]*2)
-    try:
-        return os.stat(f'{root}/{path}.file').st_sizea
-    except:
-        pass
-
-def get_file_mimetype(name):
-    mimetype = mimetypes.guess_type(name, strict=False)[0] if name else True
-    return mimetype
-
-def validate_file(filename: str, root: str = 'rest/static/files', download=True, mimetype=True) -> HTTPResponse:
-    path = '/'.join([filename,]*2)
-
-    name = get_filename(filename)
-    mimetype = mimetype if mimetype and mimetype is not True else get_file_mimetype(name)
-    
-    ret = static_file(
-        f'{path}.file',
-        root=root,
-        download=name if name and download else download,
-        mimetype='auto' if mimetype is True else mimetype
-    )
-    if isinstance(ret, HTTPError):
-        return abort(404, f"No such `Upload`: {filename}")
-
-    _bytes = blake_file(f'{path}.file', person='upload'.encode('utf-8'), root=root)
-    _b32 = bytes_to_base32(_bytes)
-    if _b32 != filename:
-        return abort(410, f"Uploaded content differs")
-    return ret
-
-
-def validate_parameter(request: LocalRequest, name: str) -> bytes:
-    if name not in request.params:
-        return abort(400, f"Missing parameter: '{name}'")
-
-    # TODO: what is correct overhead for form content?
-    OVERHEAD = 1024
-    content: bytes = request.query.get(name, None)
-    content_length = request.content_length
-    if content_length == -1:
-        return abort(418, f"Content-Length must be specified")
-    if content_length > CLIP_SIZE_LIMIT + OVERHEAD:
-        return abort(418, f"Content-Length can not exceed {CLIP_SIZE_LIMIT*3} bytes")
-
-    # TODO: add test for both query/form param
-    if 'multipart/form-data' in request.content_type:
-        # TODO: what about binary data ?
-        content: bytes = (content or request.params[name].encode('utf-8'))
-    else:
-        content: bytes = (content or request.params[name].encode('latin-1'))
-
-    if len(content) > CLIP_SIZE_LIMIT:
-        return abort(418, f"Paste can not exceed {CLIP_SIZE_LIMIT} bytes")
-    return content
-
-
-def validate_url(url: str) -> str:
-    scheme, netloc, path, params, query, fragment = urlparse(url)
-
-    if not scheme: return abort(400, "URL has no scheme")
-
-    if scheme == 'file' and not path: return abort(400, "File URL has no path")
-
-    if scheme in ('http', 'https') and not netloc: return abort(400, "HTTP(S) URL has no netloc")
-
-    if netloc:
-        try:
-            user_info, loc = netloc.rsplit('@', 1)
-        except ValueError:
-            user_info = ''
-            loc = ''
-        if user_info:
-            user_info = quote(user_info, safe=URL_SAFE)
-            netloc = f"{user_info}@{''.join(loc)}"
-        else:
-            # TODO: do this properly, ie, valid dns-name/ip/port etc
-            netloc = quote(netloc, safe=URL_SAFE)
-    
-    path = quote(path, safe=URL_SAFE)
-    params = quote_plus(params, safe=URL_SAFE)
-    query = quote(query, safe=URL_SAFE)
-    fragment = quote(fragment, safe=URL_SAFE)
-    
-    url = f'{scheme}://{netloc}{path}{params}'
-    if query:
-        url = f'{url}?{query}'
-    if fragment:
-        url = f'{url}#{fragment}'
-    return url

+ 0 - 25
robots.txt

@@ -1,25 +0,0 @@
-User-agent: *
-Disallow: /gogsadmin/*/commits/
-Disallow: /gogsadmin/*/src/
-Disallow: /.github/
-Disallow: /.phan/
-Disallow: /assets/
-Disallow: /backup/
-Disallow: /bin/
-Disallow: /cache/
-Disallow: /logs/
-Disallow: /system/
-Disallow: /tests/
-Disallow: /tmp/
-Disallow: /user/
-Disallow: /vendor/
-Disallow: /webserver-configs/
-Allow: /user/pages/
-Allow: /user/themes/
-Allow: /user/images/
-Allow: /
-Allow: /gogsadmin/
-Allow: /feed/
-Allow: *.css$
-Allow: *.js$
-Allow: /system/*.js$

+ 0 - 0
test/__init__.py


+ 0 - 112
test/rest/rfc2396.py

@@ -1,112 +0,0 @@
-# https://www.ietf.org/rfc/rfc2396.txt
-"""   
-The following definitions are common to many elements:
-
-      alpha    = lowalpha | upalpha
-
-      lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |
-                 "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |
-                 "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
-
-      upalpha  = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
-                 "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
-                 "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
-
-      digit    = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
-                 "8" | "9"
-                 
-      alphanum = alpha | digit
-
-      uric          = reserved | unreserved | escaped
-
-      reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
-                    "$" | ","
-
-      unreserved  = alphanum | mark
-
-      mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
-
-      escaped     = "%" hex hex
-      hex         = digit | "A" | "B" | "C" | "D" | "E" | "F" |
-                            "a" | "b" | "c" | "d" | "e" | "f"
-
-      control     = <US-ASCII coded characters 00-1F and 7F hexadecimal>
-
-      space       = <US-ASCII coded character 20 hexadecimal>
-
-      delims      = "<" | ">" | "#" | "%" | <">
-
-      unwise      = "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
-
-      <scheme>:<scheme-specific-part>
-
-      <scheme>://<authority><path>?<query>
-
-      absoluteURI   = scheme ":" ( hier_part | opaque_part )
-
-      hier_part     = ( net_path | abs_path ) [ "?" query ]
-
-      net_path      = "//" authority [ abs_path ]
-
-      abs_path      = "/"  path_segments
-
-      opaque_part   = uric_no_slash *uric
-
-      uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
-                      "&" | "=" | "+" | "$" | ","
-    
-      scheme        = alpha *( alpha | digit | "+" | "-" | "." )
-
-      authority     = server | reg_name
-
-      Within the authority component, the characters ";", ":",
-   "@", "?", and "/" are reserved.
-      
-      reg_name      = 1*( unreserved | escaped | "$" | "," |
-                          ";" | ":" | "@" | "&" | "=" | "+" )
-    
-      <userinfo>@<host>:<port>
-
-      server        = [ [ userinfo "@" ] hostport ]
-
-      userinfo      = *( unreserved | escaped |
-                         ";" | ":" | "&" | "=" | "+" | "$" | "," )
-    
-      hostport      = host [ ":" port ]
-      host          = hostname | IPv4address
-      hostname      = *( domainlabel "." ) toplabel [ "." ]
-      domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
-      toplabel      = alpha | alpha *( alphanum | "-" ) alphanum
-
-      IPv4address   = 1*digit "." 1*digit "." 1*digit "." 1*digit
-      port          = *digit
-
-"""
-# uric := URI characters
-lowalpha = 'a-z'
-upalpha = 'A-Z'
-alpha = f'{lowalpha}{upalpha}'
-digit = '0-9'
-alphanum = f'{alpha}{digit}'
-reserved_no_slash = r';\?:@&=\+\$,'
-reserved = f'/{reserved_no_slash}'
-_mark = r"\-_\.!~*'()"
-hex = f'a-fA-F{digit}'
-escaped = f"%[{hex}][{hex}]"
-unreserved = f'{alphanum}{_mark}'
-control = b''
-#for i in chain(range(int('0x1F', 0)+1), [int('0x7F', 0)]):
-#    control += control + bytes(i)
-control = control.decode('ascii')
-space = ' '
-delims = r'\<\>#%"'
-unwise = r'{}|\\\^\[\]`'
-excluded = f'{control}{space}{delims}{unwise}'
-scheme = f'[{alpha}][{alphanum}+.-]*'
-authority_reserved = r';:@\?/'
-reg_name_aux = r'\$,;:@&=\+'
-reg_name = f'([{unreserved}]|{escaped}|{reg_name_aux})+'
-user_info_aux = r'\$,;:&=\+'
-user_info = f'([{unreserved}]|{escaped}|{user_info_aux})+'
-server = r'({user_info}@)?'
-# ... gave up

File diff suppressed because it is too large
+ 0 - 308
test/rest/test_hash_util.py


+ 0 - 72
test/rest/test_url.py

@@ -1,72 +0,0 @@
-#
-# Copyright (c) Daniel Sheffield 2023
-#
-# All rights reserved
-#
-# THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
-
-from pytest import mark, raises
-from bottle import HTTPError
-from rest.validate import validate_url
-
-@mark.parametrize('url, expected', [
-    ['file:///',]*2,
-    ['file:///a/b/c',]*2,
-    ['https://shandan.one',]*2,
-    ['https://www.shandan.one',]*2,
-    ['https://www.shandan.one/clip?id=123',]*2,
-    
-    # empty query
-    ['https://www.shandan.one?', 'https://www.shandan.one',],
-
-    # empty fragment
-    ['https://www.shandan.one/clip?id=123#', 'https://www.shandan.one/clip?id=123',],
-
-    # no double slash
-    #['file:/a/b/c', (HTTPError, ""),],
-    #['file:/abc', (HTTPError, ""),],
-
-    # no scheme
-    ['/a/b/c', (HTTPError, 400, "URL has no scheme"),],
-
-    # no file path scheme
-    ['file:', (HTTPError, 400, "File URL has no path"),],
-    
-    # no HTTPS domain
-    #['https://abc?id=1:', (HTTPError, 400, "HTTP(S) URL has no netloc"),],
-    
-    # conecutive dots
-    #['https://shandan.one/abc..id', 'https://shandan.one/abc..id',],
-
-    # unescaped char in reg_name
-    # TODO: should be invalid because netloc must be a domain name or ip ?
-    ['https://🌚.shandan.one', 'https://%F0%9F%8C%9A.shandan.one',],
-
-    # @ in user_info not allowed
-    # TODO: check this - final @ should not be encoded ?
-    ['https://user@mail@www.shandan.one','https://user%40mail@www.shandan.one'],
-
-    # delimiters
-    # TODO: should < be translated to %3C ?
-    ['https://www.shandan.one?a<b', 'https://www.shandan.one?a<b'],
-
-    # more delimiters
-    ['https://www.shandan.one/clip?proportion=69%', 'https://www.shandan.one/clip?proportion=69%'],
-
-    # fragment before end of reference URI
-    ['https://www.shandan.one/tiny#url?id=123', 'https://www.shandan.one/tiny#url?id=123'],
-])
-def test_validate_url_invalid(url: str, expected: str):
-    if isinstance(expected, tuple):
-        exp_exception, *ex_args = expected
-    else:
-        exp_exception = None
-    
-    if not exp_exception:
-        assert validate_url(url) == expected
-        return
-
-    with raises(exp_exception) as ex:
-        validate_url(url)
-    
-    assert list(ex.value.args) == ex_args

+ 0 - 8
util-sqlpage/clip/Index.sql

@@ -1,8 +0,0 @@
-SET ":inner" = CASE COALESCE(:content,'') <> '' AND COALESCE(:action, '') = 'Paste'
-  WHEN TRUE THEN 'sqlpage/save.sql'
-  ELSE CASE COALESCE(:hash, '')
-    WHEN '' THEN 'sqlpage/Link.sql'
-    ELSE 'sqlpage/link.sql'
-  END
-END;
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;

+ 0 - 47
util-sqlpage/clip/form.sql

@@ -1,47 +0,0 @@
-SET ":view" = COALESCE(:content, '') <> '' AND COALESCE(:action, '') <> 'Edit as New';
-SELECT 'button' AS component;
-SELECT 'Open' AS title
-, 1 AS width
-, '/clip?action=open' AS link
-;
-SELECT 'New' AS title
-, 1 AS width
-, 'gray-500' AS color
-, '/clip' AS link
-;
-
-SELECT 'form' AS component
-, '/clip/' AS action
-, CASE :view WHEN TRUE THEN 'Edit as New' ELSE 'Paste' END AS validate
-, :tabler_color AS validate_color
-, 'post' AS method
-;
-SELECT 'Paste' AS value
-, '' AS label
-, 'hidden' type
-, 'action' AS name
-WHERE NOT :view
-;
-SELECT 'Edit as New' AS value
-, '' AS label
-, 'hidden' AS type
-, 'action' AS name
-WHERE :view
-;
-SELECT :hash AS value
-, '' AS label
-, 'hidden' AS type
-, 'hash' AS name
-WHERE :view
-;
-SELECT 'Paste something here...' AS placeholder
-, 'content' AS name
-, 'textarea' AS type
-, '' AS label
-, 12 AS rows
-, :view::bool AS disabled
-, CASE COALESCE(:action, '')
-  WHEN 'New' THEN NULL
-  ELSE :content
-END AS value
-;

+ 0 - 28
util-sqlpage/clip/index.sql

@@ -1,28 +0,0 @@
-SET ":title" = 'Clip';
-SET ":tool" = 'clip';
-SET ":hash" = COALESCE(:hash, $hash, '');
-SET ":hash" = sqlpage.url_encode(:hash);
-SET ":color" = '#2fb344';
-SET ":tabler_color" = 'green';
-SET ":image" = '/static/clip/clip-favicon_square.svg';
-SET ":favicon" = :image;
-SET ":manifest" = '/static/clip/manifest.json';
-SET ":action" = COALESCE(:action, $action, '');
-SET ":inner" = (CASE :action
-  WHEN 'open' THEN 'sqlpage/Open.sql'
-  ELSE 'clip/Index.sql'
-END);
-SET ":spinner" = COALESCE(:content,'') <> '' AND COALESCE(:action, '') = 'Paste';
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/theme.sql') AS properties;
-SELECT 'loader-start' AS component
-, 'lg' AS size
-, :tabler_color AS color
-, '' AS spinner
-WHERE :spinner
-;
-SELECT 'progress' AS component
-, 'lg' AS size
-, :tabler_color AS color
-WHERE :spinner
-;
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;

+ 0 - 8
util-sqlpage/clip/save.sql

@@ -1,8 +0,0 @@
-INSERT INTO clip (hash, content, qr, created) VALUES (:hash, :content, :qr, CURRENT_TIMESTAMP)
-ON CONFLICT DO
-UPDATE SET
-  content = excluded.content,
-  created = excluded.created,
-  qr = excluded.qr
-WHERE excluded.created > clip.created;
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/link.sql') AS properties;

+ 0 - 11
util-sqlpage/code/Index.sql

@@ -1,11 +0,0 @@
-SET ":inner" = CASE :has_post_params
-  WHEN 1 THEN CASE COALESCE(:hash, '')
-    WHEN '' THEN 'code/save.sql'
-    ELSE 'code/form-fuel.sql'
-  END
-  ELSE CASE COALESCE(:hash, '')
-    WHEN '' THEN 'code/recent.sql'
-    ELSE 'code/form-fuel.sql'
-  END
-END;
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;

+ 0 - 67
util-sqlpage/code/form-fuel.sql

@@ -1,67 +0,0 @@
-SET ":created" = COALESCE(:created, (SELECT created FROM code WHERE hash = :hash));
-SET ":expiry" = COALESCE(:expiry, (SELECT expiry FROM code_detail WHERE hash = :hash));
-SET ":value" = COALESCE(:value, (SELECT value FROM code_detail WHERE hash = :hash));
-SET ":store" = COALESCE(:store, json_array((SELECT store FROM code_detail WHERE hash = :hash)));
-SET ":used" = COALESCE(:used, (SELECT used FROM code_detail WHERE hash = :hash));
-SET ":type" = COALESCE(:type, json_array((SELECT type FROM code_detail WHERE hash = :hash)));
-SET ":title" = COALESCE(:type->>0, 'New')||' Voucher';
-SET ":type" = COALESCE(:type, json_array('Fuel'));
-SET ":content" = (SELECT json(content) FROM code WHERE hash = :hash);
-SET ":validate" = 'Update';
-SET ":action" = (CASE COALESCE(:action, '') WHEN '' THEN NULL ELSE :action END);
-SET ":method" = 'post';
-SET ":preview" = (SELECT 'data:image/svg+xml;base64,'||svg FROM code WHERE hash = :hash);
-SET ":autofill" = TRUE;
-SET ":filter_config" = '[
-  {"name": "store[]", "required": true},
-  {"name": "value", "required": true},
-  {"name": "expiry", "required": true},
-    { "name": "used", "label": "Used",
-      "type": "radio", "value": "true",
-      "width": 2, "checked": "'||CASE COALESCE(:used,'')
-  WHEN 'true' THEN 'true' ELSE 'false' END||'"
-    },
-    { "name": "used", "label": "Not Used",
-      "type": "radio", "value": "false",
-      "width": 2, "checked": "'||CASE COALESCE(:used,'')
-  WHEN 'true' THEN 'false' ELSE 'true' END||'"
-    }
-]';
-
-SET ":inner" = CASE COALESCE(:action, '')
-  WHEN 'Update' THEN 'code/update.sql'
-  ELSE 'code/form.sql'
-END;
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/theme.sql') AS properties
-WHERE COALESCE(:action, '') <> 'Update';
-
-SELECT 'card' AS component
-, 2 AS columns
-WHERE COALESCE(:action, '') <> 'Update';
-;
-SELECT COALESCE(:store->>'0'||' ', '') || COALESCE(:expiry, :created, '') AS title
-, '
-
-Type: '||COALESCE(:type->>0,'')||'
-
-Value: $'||COALESCE(:value,'')||'
-
-Expires: '||COALESCE(:expiry,'')||'
-
-Submitted: '||COALESCE(:created,'')||'
-
-
-| Type | Content |
-|:-----|:--------|
-| ' || COALESCE(:content->>'format', '') || ' | ' || COALESCE(:content->>'content', '') || ' |
-' AS description_md
-, :preview AS top_image
-, :tabler_color AS color
-WHERE COALESCE(:action, '') <> 'Update';
-;
-
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;
-
-SELECT 'table' AS component;
-SELECT * FROM code_detail
-WHERE hash = :hash;

+ 0 - 59
util-sqlpage/code/form.sql

@@ -1,59 +0,0 @@
-SET ":filter_options" = (
-  SELECT json_group_array(json_object('name', q.k, 'options', q.o))
-  FROM (
-    SELECT options.k, jsonb_group_array(
-      jsonb_object('label', v, 'value', v, 'selected', s OR CASE :autofill WHEN TRUE THEN c = 1 ELSE FALSE END)
-      ORDER BY v) o
-    FROM (
-      SELECT DISTINCT k, v, s, count(v) OVER (
-        PARTITION BY k ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
-      ) c FROM (
-        SELECT 'store'||'[]' k, value v, TRUE s FROM (SELECT value FROM json_each($store))
-        UNION
-        SELECT 'store'||'[]', store, NULL s FROM code_detail
-        UNION
-        SELECT 'type'||'[]' k, value v, TRUE s FROM (SELECT value FROM json_each($type))
-        UNION
-        SELECT 'type'||'[]', type, NULL s FROM code_detail
-        UNION
-        SELECT 'value', value, CASE COALESCE($value, '') WHEN '' THEN FALSE ELSE value = :value END s FROM code_detail
-      )
-    ) options
-    WHERE v IS NOT NULL
-    GROUP BY options.k
-  ) q
-);
-
-SELECT 'form' AS component
-, '/code/' AS action
-, :validate AS validate
-, :tabler_color AS validate_color
-, :method AS method
-;
-SELECT COALESCE(c.v->>'name', j.v->>'name') AS name
---, fo.j#>>'{options}' AS label
-, COALESCE(c.v->>'label', j.v->>'label') AS label
-, COALESCE(c.v->>'type', j.v->>'type') AS type
-, COALESCE(c.v->>'dropdown', j.v->>'dropdown') AS dropdown
-, COALESCE(c.v->>'create_new', j.v->>'create_new') AS create_new
-, COALESCE(c.v->>'multiple', j.v->>'multiple') AS multiple
-, COALESCE(c.v->>'placeholder', j.v->>'placeholder') AS placeholder
-, COALESCE(c.v->>'width', j.v->>'width') AS width
-, o.j->>'options' AS options
-, COALESCE(c.v->>'prefix', j.v->>'prefix') AS prefix
-, COALESCE(c.v->>'value', j.v->>'value', v.v) AS value
-, CASE COALESCE(c.v->>'type', j.v->>'type')
-    WHEN 'checkbox' THEN COALESCE(c.v->>'checked', v.v) = 'true'
-    WHEN 'radio' THEN COALESCE(c.v->>'checked', v.v) = 'true'
-    ELSE NULL
-  END AS checked
-, j.v->>'formaction'
-, COALESCE(c.v->>'required', j.v->>'required') AS required
-FROM (SELECT value v FROM json_each(sqlpage.read_file_as_text('code/json/filters.json'))) j
-FULL JOIN (SELECT value v FROM json_each(:filter_config)) c
-ON (j.v->>'name' = c.v->>'name')
-LEFT JOIN (SELECT value j FROM json_each(:filter_options)) o
-ON (o.j->>'name') = j.v->>'name'
-LEFT JOIN (SELECT "key" k, value v FROM json_each(sqlpage.variables())) v
-ON v.k = j.v->>'name' OR v.k = c.v->>'name'
-;

+ 0 - 16
util-sqlpage/code/index.sql

@@ -1,16 +0,0 @@
-SET ":has_post_params" = (SELECT 1 FROM json_each(sqlpage.variables('post')) LIMIT 1);
-SET ":content" = (SELECT json_patch(sqlpage.variables('post'), '{
-    "has_post_params": null,
-    "timestamp": null
-}'));
-SET ":title" = 'Code';
-SET ":tool" = 'code';
-SET ":hash" = COALESCE($hash, '');
-SET ":hash" = sqlpage.url_encode(:hash);
-SET ":link" = '/code';
-SET ":color" = '#f59f00';
-SET ":tabler_color" = 'azure';
-SET ":image" = '/static/code/qr.svg';
-SET ":favicon" = :image;
---SET ":manifest" = '/static/upload/manifest.json';
-SELECT 'dynamic' AS component, sqlpage.run_sql('code/Index.sql') AS properties;

+ 0 - 24
util-sqlpage/code/json/filters.json

@@ -1,24 +0,0 @@
-[
-    { "name": "type[]", "label": "Type",
-      "type": "select", "dropdown": true, "create_new": true,
-      "width": 2
-    },
-    { "name": "store[]", "label": "Store",
-      "type": "select", "dropdown": true, "create_new": true,
-      "width": 2
-    },
-    { "name": "value", "label": "Value", "prefix": "$",
-      "type": "select", "dropdown": true, "create_new": true,
-      "width": 2
-    },
-    { "name": "expiry", "label": "Expiry",
-      "type": "date",
-      "width": 2
-    },
-    { "name": "action", "label": "", "value": "Update",
-      "type": "hidden"
-    },
-    { "name": "hash", "label": "",
-      "type": "hidden"
-    }
-]

+ 0 - 9
util-sqlpage/code/new.sql

@@ -1,9 +0,0 @@
-INSERT INTO code(hash, content, svg, created)
-VALUES (:hash, :content, :preview, CURRENT_TIMESTAMP)
-ON CONFLICT DO
-UPDATE SET
-  content = excluded.content,
-  created = excluded.created,
-  svg = excluded.svg
-WHERE excluded.created > code.created;
-SELECT 'json' AS component, json('"'||:hash||'"') AS contents;

+ 0 - 52
util-sqlpage/code/recent.sql

@@ -1,52 +0,0 @@
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/theme.sql') AS properties;
-SET ":filter_config" = '[
-  {"name": "expiry", "type": "hidden"},
-  {"name": "value", "type": "hidden"},
-  {"name": "type[]", "label": "Type",
-   "type": "select", "dropdown": true,
-   "multiple": true,
-   "width": 3
-  },
-  {"name": "store[]", "create_new": false,
-   "multiple": true,
-   "width": 3
-  },
-  {"name": "expired", "label": "Show Expired",
-   "type": "checkbox", "value": "true",
-   "width": 2
-  },
-  {"name": "used", "label": "Show Used",
-   "type": "checkbox", "value": "true",
-   "width": 2
-  }
-]';
-SET ":validate" = 'Apply';
-SET ":action" = 'Apply';
-SET ":method" = 'get';
-SET ":autofill" = FALSE;
-SELECT 'dynamic' AS component, sqlpage.run_sql('code/form.sql') AS properties;
-
-SELECT 'list' AS component;
-SELECT COALESCE(type||' ','') || COALESCE(store||' ', '') || COALESCE(expiry, created) AS title
-, COALESCE(content->>'content'||' ', '') || COALESCE(content->>'type', '') AS description
-, '/code?hash='||c.hash AS link
-FROM code c
-LEFT JOIN code_detail cd
-ON c.hash = cd.hash
-WHERE (
-    cd.expiry IS NULL OR COALESCE($expired, 'false') = 'true' OR date(cd.expiry) > date(datetime(CURRENT_TIMESTAMP, 'localtime'))
-) AND (
-    cd.used IS NULL OR COALESCE($used||'', 'false') = 'true' OR COALESCE(cd.used||'','') = 'false'
-) AND (
-    CASE COALESCE($type, '')
-      WHEN '' THEN TRUE
-      ELSE $type IS NULL OR cd.type IN (SELECT value FROM json_each($type))
-    END
-) AND (
-    CASE COALESCE($store, '')
-      WHEN '' THEN TRUE
-      ELSE $store IS NULL OR cd.store IN (SELECT value FROM json_each($store))
-    END
-)
-ORDER BY expiry, created DESC, type, c.hash NULLS FIRST
-;

+ 0 - 12
util-sqlpage/code/save.sql

@@ -1,12 +0,0 @@
-SET ":request" = json_object(
-    'method', 'POST',
-    'url', 'https://shandan.one/code/meta',
-    'headers', json_object(),
-    'body', json_object(
-        'data', json(:content)
-    )
-);
-SET ":meta" = sqlpage.fetch(:request);
-SET ":hash" = :meta->>'hash';
-SET ":preview" = :meta->>'preview';
-SELECT 'dynamic' AS component, sqlpage.run_sql('code/new.sql') AS properties;

+ 0 - 12
util-sqlpage/code/update.sql

@@ -1,12 +0,0 @@
-INSERT INTO code_detail(hash, type, store, value, expiry, used)
-VALUES (:hash, :type->>0, :store->>0, :value, :expiry, :used)
-ON CONFLICT DO
-UPDATE SET
-  type = excluded.type,
-  store = excluded.store,
-  value = excluded.value,
-  expiry = excluded.expiry,
-  used = excluded.used
-WHERE TRUE
-;
-SELECT 'redirect' AS component, '/code' AS link;

+ 0 - 8
util-sqlpage/goto/Index.sql

@@ -1,8 +0,0 @@
-SET ":inner" = CASE COALESCE(:content,'') <> '' AND COALESCE(:action, '') = 'Shrtn It!'
-  WHEN TRUE THEN 'sqlpage/save.sql'
-  ELSE CASE COALESCE(:hash,'')
-    WHEN '' THEN 'sqlpage/Link.sql'
-    ELSE 'sqlpage/link.sql'
-  END
-END;
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;

+ 0 - 36
util-sqlpage/goto/form.sql

@@ -1,36 +0,0 @@
-SET ":view" = COALESCE(:content, '') <> '';
-SELECT 'button' AS component;
-SELECT 'Open' AS title
-, 1 AS width
-, '/goto?action=open' AS link
-;
-SELECT 'New' AS title
-, 1 AS width
-, 'gray-500' AS color
-, '/goto' AS link
-;
-
-SELECT 'form' AS component
-, '/goto/' AS action
-, 'Shrtn It!' AS validate
-, :tabler_color AS validate_color
-, 'post' AS method
-WHERE NOT :view
-;
-SELECT 'action' AS name
-, '' AS label
-, 'hidden' AS type
-, 'Shrtn It!' AS value
-WHERE NOT :view
-;
-SELECT 'content' AS name
-, '' AS label
-, 'input' AS type
-, :content AS value
-, 'Paste URL here...' AS placeholder
-WHERE NOT :view
-;
-
-SELECT 'dynamic' AS component, sqlpage.run_sql('goto/preview.sql') AS properties
-WHERE :content <> ''
-;

+ 0 - 32
util-sqlpage/goto/index.sql

@@ -1,32 +0,0 @@
-SET ":title" = 'GoTo';
-SET ":tool" = 'goto';
-SET ":hash" = COALESCE($hash, '');
-SET ":hash" = sqlpage.url_encode(:hash);
-SET ":go" = COALESCE($go, '');
-SET ":color" = '#dc4e41';
-SET ":tabler_color" = 'google';
-SET ":image" = '/static/goto/chain-link2fr-3-2.svg';
-SET ":favicon" = :image;
-SET ":manifest" = '/static/goto/manifest.json';
-SET ":action" = COALESCE($action, :action, '');
-SET ":inner" = (CASE :action
-  WHEN 'open' THEN 'sqlpage/Open.sql'
-  ELSE 'goto/Index.sql'
-END);
-SELECT 'dynamic' AS component, sqlpage.run_sql('goto/redirect.sql') AS properties
-WHERE (:go = 'true' AND :hash <> '');
-
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/theme.sql') AS properties;
-SET ":spinner" = COALESCE(:content,'') <> '' AND COALESCE(:action, '') = 'Shrtn It!';
-SELECT 'loader-start' AS component
-, 'lg' AS size
-, :tabler_color AS color
-, '' AS spinner
-WHERE :spinner
-;
-SELECT 'progress' AS component
-, 'lg' AS size
-, :tabler_color AS color
-WHERE :spinner
-;
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;

+ 0 - 12
util-sqlpage/goto/preview.sql

@@ -1,12 +0,0 @@
-SET ":title" = :preview->>'title';
-SET ":image" = :preview->>'img';
-SET ":domain" = :preview->>'domain';
-SELECT 'card' AS component
-, 2 AS columns
-;
-SELECT :content AS link
-, :title AS title
-, :image AS top_image
-, :domain AS description
-, :tabler_color AS color
-;

+ 0 - 11
util-sqlpage/goto/redirect.sql

@@ -1,11 +0,0 @@
-SET ":content" = (SELECT content FROM goto WHERE hash = :hash);
-SET ":icon" = 'error-404';
-SET ":status" = '404';
-SET ":title" = :status||' - Not found';
-SET ":description" = 'No such '||:tool||': '||:hash;
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/alert.sql') AS properties
-WHERE COALESCE(:content,'') = ''
-;
-SELECT 'redirect' AS component
-, :content AS link
-;

+ 0 - 9
util-sqlpage/goto/save.sql

@@ -1,9 +0,0 @@
-INSERT INTO goto (hash, content, qr, preview, created) VALUES (:hash, :content, :qr, json(:preview), CURRENT_TIMESTAMP)
-ON CONFLICT DO
-UPDATE SET
-  content = excluded.content,
-  created = excluded.created,
-  qr = excluded.qr,
-  preview = excluded.preview
-WHERE excluded.created > goto.created;
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/link.sql') AS properties;

+ 0 - 22
util-sqlpage/sqlpage/Link.sql

@@ -1,22 +0,0 @@
-SELECT 'loader-stop' AS component
-WHERE :spinner;
-
-SET ":link" = COALESCE(:link, 'https://shandan.one/'||:tool);
-SELECT 'text' AS component
-, '<div class="pure-g" sty>
-  <div class="pure-u-1">
-    <div class="pure-button" style="margin: 1em 0 0; background: '||:color||';">
-      <a href="'||:link||'" style="color: floralwhite;">'||:link||'</a>
-    </div>
-  </div>
-</div>' AS html
-;
-
-SELECT 'dynamic' AS component, sqlpage.run_sql('sqlpage/QR.sql') AS properties
-WHERE COALESCE (:hash, '') = '';
-
-SET ":inner" = CASE COALESCE(:hash, '')
-  WHEN '' THEN :tool||'/form.sql'
-  ELSE 'sqlpage/validate.sql'
-END;
-SELECT 'dynamic' AS component, sqlpage.run_sql(:inner) AS properties;

+ 0 - 46
util-sqlpage/sqlpage/Open.sql

@@ -1,46 +0,0 @@
-SELECT 'text' AS component
-, '<style>
-.form-fieldset {
-    border: none;
-}
-.form-fieldset .row {
-    align-items: center;
-    justify-content: center;
-}
-input[name=hash] {
-    text-align: center;
-}
-</style>' AS html
-;
-SELECT 'button' AS component
-, 'center' AS justify
-, 'lg' AS size
-;
-SELECT 'New '||$title AS title
-, 2 AS width
-, 'gray-500' AS color
-, '/'||$tool AS link
-;
-SELECT 'form' AS component
-, $tool||'-open' AS id
-, '/'||$tool||'/' AS action
-, '' AS validate
-, 'get' AS method
-;
-SELECT 'C0DE' AS placeholder
-, 'input' AS type
-, '' AS label
-, 'hash' AS name
-, 2 AS width
-, TRUE AS autofocus
-;
-SELECT 'button' AS component
-, 'center' AS justify
-, 'lg' AS size
-, 2 AS width
-;
-SELECT 'Submit' AS title
-, 1 AS width
-, $tabler_color AS color
-, $tool||'-open' AS form
-;

+ 0 - 11
util-sqlpage/sqlpage/QR.sql

@@ -1,11 +0,0 @@
-SET ":qr" = COALESCE(:qr, '<img src="/static/'||:tool||'/qr.svg"/>');
-
-SELECT 'text' AS component
-, '<div class="pure-g" sty>
-  <div class="pure-u-1">
-    <details><summary>Show QR code ...</summary>
-'||:qr||'
-    </details>
-  </div>
-</div>' AS html
-;

+ 0 - 50
util-sqlpage/sqlpage/Style.sql

@@ -1,50 +0,0 @@
-SELECT 'text' AS component
-, '<style>
-/* loader container */
-.sqlpage-loader-container {
-  position: fixed;
-  text-align: center;
-  left: 50vw;
-  top: 50vh;
-  margin-top: -5.5em;
-  margin-left: -87.5px;
-  padding-bottom: 2em;
-  height: 9em;
-  width: 175px;
-}
-.sqlpage-loader-container:has(.status) {
-  position: inherit;
-  text-align: inherit;
-  left: inherit;
-  top: inherit;
-  margin-top: inherit;
-  margin-left: inherit;
-  padding-bottom: inherit;
-  height: inherit;
-  width: inherit;
-}
-div.sqlpage-loader-start:has(+ .sqlpage-loader-stop) {
-  /* hide if followed by sqlpage-loader-stop */
-  display: none;
-}
-/* end loader container */
-
-/* progress container */
-.sqlpage-progress-container {
-  margin: 1em 0 1em;
-}
-div.sqlpage-progress-container:has(+ .sqlpage-progress-container) {
-  /* hide if followed by sqlpage-progress-container */
-  display: none;
-}
-.sqlpage-progress-container label {
-  text-align:left;
-  color: var(--tblr-text-primary);
-}
-.sqlpage-progress-container label:after {
-  content: "…";
-}
-/* end progress container */
-</style>
-' AS html
-;

+ 0 - 5
util-sqlpage/sqlpage/alert.sql

@@ -1,5 +0,0 @@
-SELECT 'alert' AS component
-, $icon AS icon
-, $title AS title
-, $description AS description
-;

Some files were not shown because too many files changed in this diff