diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-17 07:48:40 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-17 07:48:56 +0300 |
commit | a0f5bccc709866521fefeb34d6b97cafdbec6fad (patch) | |
tree | fefbc4c6cfec66bd84b85ede26a28eb1622a6940 /src | |
parent | 115602cf34dfb2f151846673468a41f16712eb49 (diff) |
Recognize and visualize "mailto:" links
"mailto:" links now have their own icon and when clicked they open the URL in the default web browser.
IssueID #5
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 5 | ||||
-rw-r--r-- | src/gmdocument.c | 32 | ||||
-rw-r--r-- | src/gmdocument.h | 2 | ||||
-rw-r--r-- | src/gmutil.c | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 41 |
5 files changed, 49 insertions, 34 deletions
@@ -787,8 +787,9 @@ iBool handleCommand_App(const char *cmd) { | |||
787 | const iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url")); | 787 | const iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url")); |
788 | iUrl parts; | 788 | iUrl parts; |
789 | init_Url(&parts, url); | 789 | init_Url(&parts, url); |
790 | if (isEmpty_String(&d->httpProxy) && | 790 | if (equalCase_Rangecc(parts.scheme, "mailto") || |
791 | (equalCase_Rangecc(parts.scheme, "http") || equalCase_Rangecc(parts.scheme, "https"))) { | 791 | (isEmpty_String(&d->httpProxy) && (equalCase_Rangecc(parts.scheme, "http") || |
792 | equalCase_Rangecc(parts.scheme, "https")))) { | ||
792 | openInDefaultBrowser_App(url); | 793 | openInDefaultBrowser_App(url); |
793 | return iTrue; | 794 | return iTrue; |
794 | } | 795 | } |
diff --git a/src/gmdocument.c b/src/gmdocument.c index b2561ee0..f8d5c7cf 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -229,6 +229,9 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
229 | else if (equalCase_Rangecc(parts.scheme, "about")) { | 229 | else if (equalCase_Rangecc(parts.scheme, "about")) { |
230 | link->flags |= about_GmLinkFlag; | 230 | link->flags |= about_GmLinkFlag; |
231 | } | 231 | } |
232 | else if (equalCase_Rangecc(parts.scheme, "mailto")) { | ||
233 | link->flags |= mailto_GmLinkFlag; | ||
234 | } | ||
232 | /* Check the file name extension, if present. */ | 235 | /* Check the file name extension, if present. */ |
233 | if (!isEmpty_Range(&parts.path)) { | 236 | if (!isEmpty_Range(&parts.path)) { |
234 | iString *path = newRange_String(parts.path); | 237 | iString *path = newRange_String(parts.path); |
@@ -258,7 +261,7 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
258 | trim_Rangecc(&desc); | 261 | trim_Rangecc(&desc); |
259 | if (!isEmpty_Range(&desc)) { | 262 | if (!isEmpty_Range(&desc)) { |
260 | line = desc; /* Just show the description. */ | 263 | line = desc; /* Just show the description. */ |
261 | link->flags |= userFriendly_GmLinkFlag; | 264 | link->flags |= humanReadable_GmLinkFlag; |
262 | } | 265 | } |
263 | else { | 266 | else { |
264 | line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */ | 267 | line = capturedRange_RegExpMatch(&m, 1); /* Show the URL. */ |
@@ -321,10 +324,11 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
321 | static const float bottomMargin[max_GmLineType] = { | 324 | static const float bottomMargin[max_GmLineType] = { |
322 | 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 1.0f | 325 | 0.0f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.5f, 1.0f |
323 | }; | 326 | }; |
324 | static const char *arrow = "\u27a4"; // "\u2192"; | 327 | static const char *arrow = "\u27a4"; |
325 | static const char *bullet = "\u2022"; | 328 | static const char *envelope = "\U0001f4e7"; |
326 | static const char *folder = "\U0001f4c1"; | 329 | static const char *bullet = "\u2022"; |
327 | static const char *globe = "\U0001f310"; | 330 | static const char *folder = "\U0001f4c1"; |
331 | static const char *globe = "\U0001f310"; | ||
328 | const float midRunSkip = 0; /*0.120f;*/ /* extra space between wrapped text/quote lines */ | 332 | const float midRunSkip = 0; /*0.120f;*/ /* extra space between wrapped text/quote lines */ |
329 | clear_Array(&d->layout); | 333 | clear_Array(&d->layout); |
330 | clearLinks_GmDocument_(d); | 334 | clearLinks_GmDocument_(d); |
@@ -356,6 +360,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
356 | run.imageId = 0; | 360 | run.imageId = 0; |
357 | enum iGmLineType type; | 361 | enum iGmLineType type; |
358 | int indent = 0; | 362 | int indent = 0; |
363 | /* Detect the type of the line. */ | ||
359 | if (!isPreformat) { | 364 | if (!isPreformat) { |
360 | type = lineType_GmDocument_(d, line); | 365 | type = lineType_GmDocument_(d, line); |
361 | if (contentLine.start == content.start) { | 366 | if (contentLine.start == content.start) { |
@@ -479,7 +484,9 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
479 | const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); | 484 | const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); |
480 | icon.text = range_CStr(link->flags & file_GmLinkFlag | 485 | icon.text = range_CStr(link->flags & file_GmLinkFlag |
481 | ? folder | 486 | ? folder |
482 | : link->flags & remote_GmLinkFlag ? globe : arrow); | 487 | : link->flags & mailto_GmLinkFlag |
488 | ? envelope | ||
489 | : link->flags & remote_GmLinkFlag ? globe : arrow); | ||
483 | if (link->flags & remote_GmLinkFlag) { | 490 | if (link->flags & remote_GmLinkFlag) { |
484 | icon.visBounds.pos.x -= gap_Text / 2; | 491 | icon.visBounds.pos.x -= gap_Text / 2; |
485 | } | 492 | } |
@@ -1114,6 +1121,7 @@ uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | |||
1114 | 1121 | ||
1115 | enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) { | 1122 | enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum iGmLinkPart part) { |
1116 | const iGmLink *link = link_GmDocument_(d, linkId); | 1123 | const iGmLink *link = link_GmDocument_(d, linkId); |
1124 | const int www_GmLinkFlag = http_GmLinkFlag | mailto_GmLinkFlag; | ||
1117 | if (link) { | 1125 | if (link) { |
1118 | const iBool isBad = (link->flags & supportedProtocol_GmLinkFlag) == 0; | 1126 | const iBool isBad = (link->flags & supportedProtocol_GmLinkFlag) == 0; |
1119 | if (part == icon_GmLinkPart) { | 1127 | if (part == icon_GmLinkPart) { |
@@ -1121,24 +1129,24 @@ enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum | |||
1121 | return tmBadLink_ColorId; | 1129 | return tmBadLink_ColorId; |
1122 | } | 1130 | } |
1123 | if (link->flags & visited_GmLinkFlag) { | 1131 | if (link->flags & visited_GmLinkFlag) { |
1124 | return link->flags & http_GmLinkFlag | 1132 | return link->flags & www_GmLinkFlag |
1125 | ? tmHypertextLinkIconVisited_ColorId | 1133 | ? tmHypertextLinkIconVisited_ColorId |
1126 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkIconVisited_ColorId | 1134 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkIconVisited_ColorId |
1127 | : tmLinkIconVisited_ColorId; | 1135 | : tmLinkIconVisited_ColorId; |
1128 | } | 1136 | } |
1129 | return link->flags & http_GmLinkFlag | 1137 | return link->flags & www_GmLinkFlag |
1130 | ? tmHypertextLinkIcon_ColorId | 1138 | ? tmHypertextLinkIcon_ColorId |
1131 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkIcon_ColorId | 1139 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkIcon_ColorId |
1132 | : tmLinkIcon_ColorId; | 1140 | : tmLinkIcon_ColorId; |
1133 | } | 1141 | } |
1134 | if (part == text_GmLinkPart) { | 1142 | if (part == text_GmLinkPart) { |
1135 | return link->flags & http_GmLinkFlag | 1143 | return link->flags & www_GmLinkFlag |
1136 | ? tmHypertextLinkText_ColorId | 1144 | ? tmHypertextLinkText_ColorId |
1137 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkText_ColorId | 1145 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkText_ColorId |
1138 | : tmLinkText_ColorId; | 1146 | : tmLinkText_ColorId; |
1139 | } | 1147 | } |
1140 | if (part == textHover_GmLinkPart) { | 1148 | if (part == textHover_GmLinkPart) { |
1141 | return link->flags & http_GmLinkFlag | 1149 | return link->flags & www_GmLinkFlag |
1142 | ? tmHypertextLinkTextHover_ColorId | 1150 | ? tmHypertextLinkTextHover_ColorId |
1143 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkTextHover_ColorId | 1151 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkTextHover_ColorId |
1144 | : tmLinkTextHover_ColorId; | 1152 | : tmLinkTextHover_ColorId; |
@@ -1147,13 +1155,13 @@ enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId, enum | |||
1147 | if (isBad) { | 1155 | if (isBad) { |
1148 | return tmBadLink_ColorId; | 1156 | return tmBadLink_ColorId; |
1149 | } | 1157 | } |
1150 | return link->flags & http_GmLinkFlag | 1158 | return link->flags & www_GmLinkFlag |
1151 | ? tmHypertextLinkDomain_ColorId | 1159 | ? tmHypertextLinkDomain_ColorId |
1152 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkDomain_ColorId | 1160 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkDomain_ColorId |
1153 | : tmLinkDomain_ColorId; | 1161 | : tmLinkDomain_ColorId; |
1154 | } | 1162 | } |
1155 | if (part == visited_GmLinkPart) { | 1163 | if (part == visited_GmLinkPart) { |
1156 | return link->flags & http_GmLinkFlag | 1164 | return link->flags & www_GmLinkFlag |
1157 | ? tmHypertextLinkLastVisitDate_ColorId | 1165 | ? tmHypertextLinkLastVisitDate_ColorId |
1158 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkLastVisitDate_ColorId | 1166 | : link->flags & gopher_GmLinkFlag ? tmGopherLinkLastVisitDate_ColorId |
1159 | : tmLinkLastVisitDate_ColorId; | 1167 | : tmLinkLastVisitDate_ColorId; |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 23ce5e8a..b453fba9 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -47,7 +47,7 @@ enum iGmLinkFlags { | |||
47 | mailto_GmLinkFlag = iBit(7), | 47 | mailto_GmLinkFlag = iBit(7), |
48 | supportedProtocol_GmLinkFlag = 0xff, | 48 | supportedProtocol_GmLinkFlag = 0xff, |
49 | remote_GmLinkFlag = iBit(9), | 49 | remote_GmLinkFlag = iBit(9), |
50 | userFriendly_GmLinkFlag = iBit(10), | 50 | humanReadable_GmLinkFlag = iBit(10), /* link has a human-readable description */ |
51 | imageFileExtension_GmLinkFlag = iBit(11), | 51 | imageFileExtension_GmLinkFlag = iBit(11), |
52 | audioFileExtension_GmLinkFlag = iBit(12), | 52 | audioFileExtension_GmLinkFlag = iBit(12), |
53 | content_GmLinkFlag = iBit(13), /* content visible below */ | 53 | content_GmLinkFlag = iBit(13), /* content visible below */ |
diff --git a/src/gmutil.c b/src/gmutil.c index 131734b2..b2c7e93f 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -132,7 +132,8 @@ const iString *absoluteUrl_String(const iString *d, const iString *urlMaybeRelat | |||
132 | iUrl rel; | 132 | iUrl rel; |
133 | init_Url(&orig, d); | 133 | init_Url(&orig, d); |
134 | init_Url(&rel, urlMaybeRelative); | 134 | init_Url(&rel, urlMaybeRelative); |
135 | if (equalCase_Rangecc(rel.scheme, "data") || equalCase_Rangecc(rel.scheme, "about")) { | 135 | if (equalCase_Rangecc(rel.scheme, "data") || equalCase_Rangecc(rel.scheme, "about") || |
136 | equalCase_Rangecc(rel.scheme, "mailto")) { | ||
136 | /* Special case, the contents should be left unparsed. */ | 137 | /* Special case, the contents should be left unparsed. */ |
137 | return urlMaybeRelative; | 138 | return urlMaybeRelative; |
138 | } | 139 | } |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 84ad1139..e632a65c 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1715,6 +1715,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
1715 | topRight_Rect(linkRect), | 1715 | topRight_Rect(linkRect), |
1716 | tmInlineContentMetadata_ColorId, | 1716 | tmInlineContentMetadata_ColorId, |
1717 | " \u2014 Fetching\u2026"); | 1717 | " \u2014 Fetching\u2026"); |
1718 | /* TODO: Show amount downloaded so far. */ | ||
1718 | } | 1719 | } |
1719 | } | 1720 | } |
1720 | else if (isHover) { | 1721 | else if (isHover) { |
@@ -1723,31 +1724,35 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
1723 | const int flags = linkFlags_GmDocument(doc, linkId); | 1724 | const int flags = linkFlags_GmDocument(doc, linkId); |
1724 | iUrl parts; | 1725 | iUrl parts; |
1725 | init_Url(&parts, url); | 1726 | init_Url(&parts, url); |
1726 | fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); | 1727 | fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); |
1727 | const iBool showHost = (!isEmpty_Range(&parts.host) && flags & userFriendly_GmLinkFlag); | 1728 | const iBool showHost = (flags & humanReadable_GmLinkFlag && |
1729 | (!isEmpty_Range(&parts.host) || flags & mailto_GmLinkFlag)); | ||
1728 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; | 1730 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; |
1729 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | 1731 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; |
1730 | iString str; | 1732 | iString str; |
1731 | init_String(&str); | 1733 | init_String(&str); |
1734 | /* Show scheme and host. */ | ||
1732 | if (run->flags & endOfLine_GmRunFlag && | 1735 | if (run->flags & endOfLine_GmRunFlag && |
1733 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || | 1736 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || |
1734 | showHost)) { | 1737 | showHost)) { |
1735 | format_String( | 1738 | format_String(&str, |
1736 | &str, | 1739 | " \u2014%s%s%s\r%c%s", |
1737 | " \u2014%s%s%s\r%c%s", | 1740 | showHost ? " " : "", |
1738 | showHost ? " " : "", | 1741 | showHost ? (flags & mailto_GmLinkFlag |
1739 | showHost ? (!equalCase_Rangecc(parts.scheme, "gemini") | 1742 | ? cstr_String(url) |
1740 | ? format_CStr("%s://%s", | 1743 | : ~flags & gemini_GmLinkFlag |
1741 | cstr_Rangecc(parts.scheme), | 1744 | ? format_CStr("%s://%s", |
1742 | cstr_Rangecc(parts.host)) | 1745 | cstr_Rangecc(parts.scheme), |
1743 | : cstr_Rangecc(parts.host)) | 1746 | cstr_Rangecc(parts.host)) |
1744 | : "", | 1747 | : cstr_Rangecc(parts.host)) |
1745 | showHost && (showImage || showAudio) ? " \u2014" : "", | 1748 | : "", |
1746 | showImage || showAudio | 1749 | showHost && (showImage || showAudio) ? " \u2014" : "", |
1747 | ? asciiBase_ColorEscape + fg | 1750 | showImage || showAudio |
1748 | : (asciiBase_ColorEscape + linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), | 1751 | ? asciiBase_ColorEscape + fg |
1749 | showImage ? " View Image \U0001f5bc" | 1752 | : (asciiBase_ColorEscape + |
1750 | : showAudio ? " Play Audio \U0001f3b5" : ""); | 1753 | linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), |
1754 | showImage ? " View Image \U0001f5bc" | ||
1755 | : showAudio ? " Play Audio \U0001f3b5" : ""); | ||
1751 | } | 1756 | } |
1752 | if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) { | 1757 | if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) { |
1753 | iDate date; | 1758 | iDate date; |